Provided
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TestingComponent),
multi: true
}
Injected NgControl
constructor(@Self() @Optional() public control: NgControl) {
this.control && (this.control.valueAccessor = this);
}
And yet something is missing here?
Although @Eliseo answer is very explanatory there is still one addition... If you want to use both external validators and internal ones then parent NgControl Validators must be set accordingly. Furthermore you need to use ngDoCheck lifecycle hook to handle NgControl touch status if you want to use validation as me below is a final working solution
@Component({
selector: 'app-testing',
templateUrl: 'testing.component.html'
})
export class TestingComponent implements ControlValueAccessor, DoCheck, AfterViewInit {
@Input()
public required: boolean;
@ViewChild('input', { read: NgControl })
private inputNgModel: NgModel;
public value: number;
public ngControlTouched: boolean;
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) this.ngControl.valueAccessor = this;
}
ngDoCheck() {
if (this.ngControlTouched !== this.ngControl.touched) {
this.ngControlTouched = this.ngControl.touched;
}
}
ngAfterViewInit() {
// Setting ngModel validators to parent NgControl
this.ngControl.control.setValidators(this.inputNgModel.validator);
}
/**
* ControlValueAccessor Implementation
* Methods below
*/
writeValue(value: number): void {
this.value = value;
this.onChange(this.value);
}
onChange: (_: any) => void = (_: any) => {};
onTouched: () => void = () => {};
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
}
// Template
<input
#input="ngModel"
type="text"
class="form-control"
[class.is-invalid]="input.invalid && (input.dirty || input.touched || ngControlTouched)"
[(ngModel)]="value"
(ngModelChange)="onChange(value)"
(blur)="onTouched()"
[required]="required"
/>
// Usage
<app-testing [(ngModel)]="propertyDetails.whatThreeWords" name="testing" required></app-testing>
You has two options:
Inject NgControl, for this you need remove the provider and make the constructor in the way
constructor(public control:NgControl){
if (this.control != null) {
this.control.valueAccessor = this;
}
}
Then you can decorate your input like
<input [ngClass]="{'ng-invalid':control.invalid,'ng-valid':control.valid...}">
Or copy the class of the customFormControl to the input.
Your input is like
<input [ngClass]="class">
If in the constructor of your custom form control import the ElementRef
constructor(private el:ElementRef){}
And create a function "copyClass"
copyClass()
{
setTimeout(()=>{
this.class=this.elementRef.nativeElement.getAttribute('class')
})
}
You can call this function in writeValue,Change and OnTouched.
The most simple example I can imagine is in this stackblitz
NOTE: If your problem is that you're using material angular in your component, the tecnique is using a customErrorMatcher, take a look to the official docs and, if you want to this answer in stackoverflow
UPDATE Another aproach is set the same validators to the input. For this, we use viewChild to get the input and, in ngAfterViewInit equals the validators
@ViewChild('input',{static:false,read:NgControl}) input
ngAfterViewInit()
{
if (this.control != null) {
this.input.control.setValidators(this.control.control.validator)
}
}
see another stackblitz
at last update if we want to has a custom error inside the control, we can use the function validate to get the control and not inject in constructor. The component becomes like
@Component({
...
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomFormControlComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => CustomFormControlComponent),
multi: true,
}]
})
export class CustomFormControlComponent implements ControlValueAccessor,
Validator, AfterViewInit {
...
control:any
@ViewChild('input', { static: false, read: NgControl }) input
constructor() {
}
ngAfterViewInit() {
this.validate(null)
}
validate(control: AbstractControl): ValidationErrors | null{
if (!this.control)
this.control=control;
if (this.control && this.input)
this.input.control.setValidators(this.control.validator)
if (control.value=="qqq")
return {error:"Inner error:The value is 1"}
return null
}
a new stackblitz
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With