I have a parent - child component with reactive forms. Child propagates changes to parent via ControlValueAccessor.
I need to pass initial value via the template with @Input annotated variable (in the real app I want to pass the initial value from ngFor hence the @Input).
I can't set the passed in initial value during formGroup creation in the child constructor because the @Input ed value is not available yet.
I also can't set it in ngOnInit for the same reason.
In afterViewInit I can do it, and it's working, BUT only if I wrap the patchValue call inside a setTimeout. Otherwise NG0100: Expression has changed after it was checked error is thrown:
ngAfterViewInit() {
console.log('ngAfterViewInit called');
// if I omit setTimeout then NG0100: ExpressionChangedAfterItHasBeenCheckedError
// is it OK for using setTimeout for this?
setTimeout(() => {
this.formGroup.patchValue({
firstName: this.initialFirstName,
});
}, 0);
}
Is it OK to use setTimeout to avoid the error, is it reliable? Is there a better solution?
The whole thing is here: https://stackblitz.com/edit/angular-ivy-rohjor?file=src/app/name-input/name-input.component.ts
CodePudding user response:
It would have worked inside ngOnInit
, but the problem is that the change handler of your control value accessor is not yet registered. You can do it inside registerOnChange
handler like this:
public registerOnChange(fn: any): void {
console.log('registerOnChange called');
this.onChange = fn;
this.setUpDefaultValues();
}
private setUpDefaultValues(): void {
this.formGroup.patchValue({
firstName: this.initialFirstName,
});
}
This way, when your subscribe handler inside the ngOnInit
method is hit, the this.onChange
will have the correct value (the one that was registered through the control value accessor).
I recommend you to stop giving default values to these control value accessor handlers because this way, you will actually get more meaningful errors (like Error: this.onChange is not a function
) that should trigger this alarm for you and let you know that you try to use the handler before it was registered:
private onChange?: (value: any | null) => void;