Reputation: 1404
There is a custom input component and it is used in a reactive form with validation:
@Component({
moduleId: module.id.toString(),
selector: 'custom-select',
templateUrl: 'custom-select.component.html',
styleUrls: ['custom-select.component.css']
})
export class CustomSelectComponent {
@Input() public items: SelectModel[];
public model: SelectModel;
constructor(private customSelectService: CustomSelectService) {
this.customSelectService.Selected.subscribe((data: SelectModel) => {
this.model = data;
});
}
public newSelect(select: SelectModel): void {
this.customSelectService.updateSelected(select);
}
}
which works fine, I am using custom-select
in a reactive form and want to validate it like below:
<custom-select id="country" [items]="selectItems" formControlName="country"></custom-select>
<div *ngIf=" myFrom.controls['country'].invalid && (myFrom.controls['country'].dirty
|| myFrom.controls['country'].touched) " class="ha-control-alert">
<div *ngIf="myFrom.controls['country'].hasError('required')">Country is required</div>
</div>
this is how I declare the form in component
this.myFrom = this.formBuilder.group({
country: [null, Validators.required],
})
but when I add formControlName
for validations, it gets error which says No value accessor for form control with name: 'country'. How should I handle this?
Upvotes: 18
Views: 22618
Reputation: 810
STEPS
import ...
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
moduleId: module.id.toString(),
selector: 'custom-select',
templateUrl: 'custom-select.component.html',
styleUrls: ['custom-select.component.css'],
// STEP 1
providers: [{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => CustomSelectComponent)
}]
})
// STEP 2
export class CustomSelectComponent implements ControlValueAccessor {
// STEP 3
onChange = (value: SelectModel) => {};
@Input() public items: SelectModel[];
public model: SelectModel;
constructor(private customSelectService: CustomSelectService) {
this.customSelectService.Selected.subscribe((data: SelectModel) => {
this.model = data;
});
}
public newSelect(select: SelectModel): void {
// STEP 5
this.onChange(select);
this.customSelectService.updateSelected(select);
}
// STEP 4
registerOnChange(fn: (value: SelectModel) => void): void {
this.onChange = fn;
}
writeValue() {}
registerOnTouched(){}
}
Don't forget to add the formControlName in the selector.
Upvotes: 8
Reputation: 145950
Add this to the providers of the child:
viewProviders: [{
provide: ControlContainer,
useExisting: FormGroupDirective
}]
The reason it fails in the first place is it doesn't look in the parent scope for FormGroup, so this just passes it down to the child.
Then you need to trick it into thinking your control has a valueAccessor - which it doesn't need because you just created a sort of 'shim'.
export const NOOP_VALUE_ACCESSOR: ControlValueAccessor =
{
writeValue(): void {},
registerOnChange(): void {},
registerOnTouched(): void {}
};
Then in the constructor for your child:
constructor(@Self() @Optional() public ngControl: NgControl)
{
if (this.ngControl)
{
this.ngControl.valueAccessor = NOOP_VALUE_ACCESSOR;
}
}
Then you can use the formControlName as normal inside the child.
If you need to pass the formControlName from the parent component (which you probably would if it was meant to be a reusable control) just create a string @Input
property and do this in the child:
<input matInput [placeholder]="desc"
[formControlName]="formControlName">
Upvotes: 2