Cannot bind ngModel since is not a property of "Component" [duplicate] - javascript

This question already has answers here:
Can't bind to 'ngModel' since it isn't a known property of 'input'
(48 answers)
Closed 5 years ago.
So, i'm trying to make a custom form component in angular 4, i added everything needed for the ngModel to work but it doesnt.
This is my child component:
export class Search extends ControlValueComponent {
}
It extends to the class ControlValueComponent that handles all the "ControlValueAccesor".
This is the class ControlValueAccesor:
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "#angular/forms";
import {forwardRef, Input} from "#angular/core";
export class ControlValueComponent implements ControlValueAccessor {
#Input() disabled: boolean;
innerValue: any = '';
static providerValueAcessor( ref: any): any {
return [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ref), multi: true }
];
}
onTouchedCallback: () => void = () => {};
onChangeCallback: (_: any) => void = () => {};
constructor() {
}
get value(): any {
return this.innerValue;
}
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any): void {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any): void {
this.onTouchedCallback = fn;
}
}
And in order to have the provider right i use the function ProviderValueAccesor (that is in the ControlValueComponent) like this
providers: ControlValueComponent.providerValueAcessor(SysSearch)
But when i add to my child component the [(ngModel)] it keeps getting the error that ngModel is not a property of the component
I try to use it like this:
<search [(ngModel)] = 'value'><search>

I already fixed the problem.
I needed to import FormsModule directly on the parent component that calls the child component in order for the ngModel to work... I did it like this:
import {FormsModule} from "#angular/forms";
#NgModule({
imports: [
FormsModule,
SearchModule
]
This way, the father component is able to use the ngmodel property. Thanks for the help

import FormsModule in your class file:
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "#angular/forms";
---->
import {ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule} from "#angular/forms";

Related

Unit Test for Validator Directive in Angular 11

I am going to write some unit tests for my validator directive, but don't know how, even after googling and reading the Angular website. I will explain the codes (just the parts that are needed) here.
Here is my component.ts code:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
import { isValidValidator } from '../../directives/is-valid.directive';
#Component({
selector: 'app-microplate',
templateUrl: './microplate.component.html',
styleUrls: ['./microplate.component.css']
})
export class MicroplateComponent implements OnInit {
form: FormGroup;
ngOnInit(): void {
this.form = new FormGroup({
columns: new FormControl('', [
isValidValidator()
])
});
}
}
Here is my directive.ts code:
import { Directive, Input } from '#angular/core';
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn } from '#angular/forms';
const { isNumbersFieldValid } = require('../utils/utils.js');
export function isValidValidator(): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
return isNumbersFieldValid(control.value) ? {isValid: {value: control.value}} : null;
};
}
#Directive({
selector: '[appIsValid]',
providers: [{provide: NG_VALIDATORS, useExisting: IsValidDirective, multi: true}]
})
export class IsValidDirective implements Validator {
#Input('appIsValid') isValid: string;
validate(control: AbstractControl): {[key: string]: any} | null {
return this.isValid ? isValidValidator()(control) : null;
}
}
Here is my directive.spec.ts code:
import { IsValidDirective } from './is-valid.directive';
describe('IsValidDirective', () => {
it('should create an instance', () => {
const directive = new IsValidDirective();
expect(directive).toBeTruthy();
});
// How can I write some unit tests here to check if my directive works well for different input strings?
});
here you can find how to test attribute directives
https://angular.io/guide/testing-attribute-directives
basically you need to create a testComponent in your spec files and apply your directive in that testComponent, your test expectation can be done on the testComponent directly

Angular click event not fired when custom input is focused

I created simple custom input eu-input element using 'ControlValueAccessor'. Now I have something like that:
<eu-input></eu-input>
<div *ngFor="let item of items" (click)="choose(item)">
<span>{{item.name}}</span>
</div>
But there is problem I can't figure out. When eu-input is focused, click event doesn't get fired, it's fired only after eu-input is blured (on second attempt).
so what could be a problem?
this is html:
<input [(ngModel)]="value"/>local: {{val}}
and this is ts file:
import { Component, OnInit, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'eu-input',
templateUrl: './eu-input.component.html',
styleUrls: ['./eu-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EuInputComponent),
multi: true,
},
],
})
export class EuInputComponent implements OnInit, ControlValueAccessor {
constructor() {}
ngOnInit(): void {}
onChange: any = () => {};
onTouch: any = () => {};
val = '';
set value(val) {
if (val !== undefined && this.val !== val) {
this.val = val;
this.onChange(val);
this.onTouch(val);
}
}
writeValue(value: any) {
this.value = value;
}
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouch = fn;
}
}
choose() method is just console.log('choose clicked')
Change eu-input component html to
<input [(ngModel)]="value" (focus)="emitFocused(true)" (blur)="emitFocused(false)"/>local: {{val}}
In eu-input TypeScript
#Output() focus = new EventEmitter();
public emitFocused(value) {
this.focus.emit(value);
}
Can be used like
<eu-input (focus)="myMethod($event)"></eu-input>

Angular: custom input with ControlValueAccessor

I'm not sure how can I use a custom component if it's wrapper under another component.
Like:
ComponentA_withForm
|
--ComponentA1_withWrapperOfCustomInput
|
--ComponentA11_withCustomInput
if I have a structure like this:
ComponentA_withForm
|
--ComponentA11_withCustomInput
Everything's fine
But for my case (tons of async data) I need a wrapper... Is it possible somehow to do this?
Here is my fiddle code:
ComponentA:
import { Component } from '#angular/core';
import { FormBuilder } from '#angular/forms';
#Component({
selector: 'my-app',
template: `<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>`
})
export class AppComponent {
form = this.fb.group({
someInput: [],
});
get formVal() {
return this.form.getRawValue();
}
constructor(private fb: FormBuilder) { }
}
ComponentA1:
import { Component } from '#angular/core';
#Component({
selector: 'custom-input-wrapper',
template: '<custom-input></custom-input>',
})
export class CustomInputWrapperComponent {
constructor() { }
}
ComponentA11:
import { Component, forwardRef } from '#angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '#angular/forms';
#Component({
selector: 'custom-input',
template: `Hey there! <button (click)="inc()">Value: {{ value }}</button>`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true,
}],
})
export class CustomInputComponent implements ControlValueAccessor {
private value = 0;
writeValue(value: number): void {
this.value = value;
}
registerOnChange(fn: (_: any) => void): void {
this.onChangeFn = fn;
}
registerOnTouched(fn: any): void {
}
inc() {
this.value = this.value + 1;
this.onChangeFn(this.value);
}
onChangeFn = (_: any) => { };
}
And here I have a working sample:
https://stackblitz.com/edit/angular-qmrj3a
so: basically removing & refactoring code not to use CustomInputWrapperComponent makes my code working. But I need this wrapper and I'm not sure how to pass formControlName then.
I don't want a dirty solution with passing parent formGroup :)
Since you don't want a dirty solution ;) , you could just implement ControlValueAccessor in the CustomInputWrapperComponent also. That way any change in the parent will be reflected in the child, any change in the child will be reflected in the parent as well with just few lines of code.
Wrapper Component
#Component({
selector: 'custom-input-wrapper',
template: '<custom-input [formControl]="value"></custom-input>',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputWrapperComponent),
multi: true,
}]
})
export class CustomInputWrapperComponent implements AfterViewInit, ControlValueAccessor {
public value = new FormControl();
constructor() { }
ngAfterViewInit() {
this.value.valueChanges.subscribe((x) => {
this.onChangeFn(x);
});
}
writeValue(value: number): void {
this.value.setValue(value);
}
registerOnChange(fn: (_: any) => void): void {
this.onChangeFn = fn;
}
registerOnTouched(fn: any): void {
}
onChangeFn = (_: any) => { };
}
Parent Template
<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>
I have made a stackbitz demo here - https://stackblitz.com/edit/angular-csaxcz
you cannot use formControlName on custom-input-wrapper because it doesn't implement ControlValueAccessor. implementing ControlValueAccessor on custom-input-wrapper might be a solution but it seems to be overkill. Instead pass the control from formGroup to custom-input-wrapper as an #Input() and pass the inputed formControl to custom-input
app.component
#Component({
selector: 'my-app',
template: `<form [formGroup]="form"><custom-input-wrapper [formCtrl]="form.get('someInput')"></custom-input-wrapper></form> <p>value is: {{formVal | json}}</p>`
})
export class AppComponent {
form = this.fb.group({
someInput: [],
});
get formVal() {
return this.form.getRawValue();
}
constructor(private fb: FormBuilder) { }
}
custom-input-wrapper.component
#Component({
selector: 'custom-input-wrapper',
template: '<custom-input [formControl]="formCtrl"></custom-input>',
})
export class CustomInputWrapperComponent {
#Input() formCtrl: AbstractControl;
constructor() { }
}
here is a working demo https://stackblitz.com/edit/angular-3lrfqv

How to create custom form control using native element HTMLElement in angular 5 6 7

it is practically the following
I want the native HTML input tag to extend the native and typescript properties
example:
from:
<input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value">
to:
<my-input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value">
or
<my-input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value"> </my-input>
[checked] and formControlName is a property that provides angular, which also would like to inherit and native features such as: type and name
I know it's a little crazy but I would like to know if it is possible
in my component I try to do the following but, it only imports the native elements of HTML but not the features that Angular provides
import {Component, ElementRef, Inject, Input, OnInit} from '#angular/core';
#Component({
selector: 'my-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class MyInputComponent extends HTMLElement implements OnInit {
#Input() checked; // angular feature that does not work
constructor() {
super();
}
ngOnInit() {
}
}
You should use ControlValueAccessor in order to use formControlName and ngModel to your component.
The below two tutorials will definitely help you to create custom form controls.
ControlValueAccsor to act your component as a form control
Using ControlValueAccessor to create custom form controls
import {Component, ElementRef, Inject, Input, OnInit} from '#angular/core';
import {ControlValueAccessor} from '#angular/forms';
#Component({
selector: 'my-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class MyInputComponent extends HTMLElement implements OnInit, ControlValueAccessor{
#Input() checked; // angular feature that does not work
constructor() {
super();
}
ngOnInit() {
}
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
touch() {
this.touched.forEach(f => f());
}
}

RadioButtons acting strange Angular 4

I'm trying to create a custom component that groups some Radio Button this way
<group>
<radio></radio>
<radio></radio>
<radio></radio>
</group
Inside of the component I add dynamically a name for the inputs, so they all have the same name and change the selected one when I click on another.
It works good if I only have one component, if i have more than one, it extends the values like if it were only a group of RadioButtons with only one name.
This is the code I'm using:
import {AfterViewInit, Component, ContentChildren, ElementRef, Input, NgModule, QueryList} from "#angular/core";
import {CommonModule} from "#angular/common";
import {ControlValueComponent} from "../shared/ControlValueComponent";
import {SysSharedModule} from "../shared/SysSharedModule";
#Component({
selector: 'sys-radio-button',
styleUrls: ['sysRadioButton.css', '../shared/sys.css'],
providers: ControlValueComponent.providerValueAcessor(SysRadioButton),
template: `
<input type="radio" id="rb{{randomId}}" [value]="val" [(ngModel)]="value">
<label for="rb{{randomId}}">{{label}}</label>
`
})
export class SysRadioButton extends ControlValueComponent {
constructor (public elem: ElementRef) {
super();
}
#Input() groupName = 'radiobutton';
#Input() val: any;
#Input() label: string;
randomId = (Math.floor(Math.random() * (1 - 10000 + 1)) + 1) * -1;
}
#Component({
selector: 'sys-radio-group',
styleUrls: ['sysRadioButton.css', '../shared/sys.css'],
providers: ControlValueComponent.providerValueAcessor(SysRadioGroup),
template: `
<div class="t{{tam}}">
<label class="header">{{header}}</label>
<div class="radioButtonContainer"></div>
</div>
`
})
export class SysRadioGroup extends ControlValueComponent implements AfterViewInit {
#Input() name: string;
#Input() header: string;
#Input() tam = '3-of-10';
#ContentChildren(SysRadioButton) radioButtons: QueryList<SysRadioButton>;
constructor (public elem: ElementRef) {
super();
}
ngAfterViewInit() {
this.addNameToInputs();
}
addNameToInputs() {
const container = this.elem.nativeElement.getElementsByClassName('radioButtonContainer')[0];
this.radioButtons.forEach(item => {
const input = item.elem.nativeElement;
input.getElementsByTagName('input')[0].name = this.name;
container.appendChild(input);
});
}
}
#NgModule({
imports: [CommonModule, SysSharedModule],
declarations: [SysRadioButton, SysRadioGroup],
exports: [SysRadioButton, SysRadioGroup]
})
export class SysRadioButtonModule {
}
And i use it like this:
<sys-radio-group header="Select your destiny" name="name1">
<sys-radio-button val="hola1" label="Label 1"></sys-radio-button>
<sys-radio-button val="hola2" label="Label 2"></sys-radio-button>
<sys-radio-button val="hola3" label="Label 3"></sys-radio-button>
<sys-radio-button val="hola4" label="Label 4"></sys-radio-button>
</sys-radio-group>
<sys-radio-group header="Select your destiny" name="name2">
<sys-radio-button val="hola1" label="Label 1"></sys-radio-button>
<sys-radio-button val="hola2" label="Label 2"></sys-radio-button>
<sys-radio-button val="hola3" label="Label 3"></sys-radio-button>
<sys-radio-button val="hola4" label="Label 4"></sys-radio-button>
</sys-radio-group>
Here are some images of how it works
This is how it is when i don't click on anything
and this is how it looks when i click on one with the same value but a different name
If i check the elements in the chrome's console, i can see how the name's are different, so i don't understand why this is happening
EDIT
The ControlValueComponent class that extends the main classes,is just the one for the custom form. This is the code:
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "#angular/forms";
import {forwardRef, Input} from "#angular/core";
export class ControlValueComponent implements ControlValueAccessor {
#Input() disabled: boolean;
innerValue: any = '';
static providerValueAcessor( ref: any): any {
return [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ref), multi: true }
];
}
onTouchedCallback: () => void = () => {};
onChangeCallback: (_: any) => void = () => {};
constructor() {
}
get value(): any {
return this.innerValue;
}
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any): void {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any): void {
this.onTouchedCallback = fn;
}
}
So here is where the "value" variable of the [(ngModel)] comes from
change ngModel to :
[(ngModel)]="val"

Categories

Resources