Reputation: 7022
I'm trying to create a custom MatFormFieldControl which also implements the ControlValueAccessor.
I wanted to start with a known use case: password form.
The Plan: to create a form field that gets a single string, but have it's own form inside it that adds a validation against the confirmPassword field.
I created a StackBlitz with a working implementation.
Every time the errorState variable is called, I check if there's a missmatch between the two fields, and update the errors on the ngControl so it can be reflected to the outside.
The error state is shown correctly at first, but if you match the passwords and than missmatch them again, this error is thrown:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: null'. Current value: 'ngIf: [object Object]'.
I couldn't find a lot of documentation about this online and I'm not sure when is the right place to add the error to the errors object
Anyone known how I can avoid this error?
thanks in advance
get errorState() {
const missmatch = this.weightForm.value.password !== this.weightForm.value.confirmPassword;
if (missmatch) {
this.ngControl.control.setErrors({ missmatch }, { emitEvent: false });
}
return (this.ngControl.errors !== null || missmatch) && !!this.ngControl.touched;
}
The idea here is to create a "separation of concerns" were the outer form doesn't know about confirmPassword since it's more of a "Validation" field instead of a "data" field if that makes sense.
Another example for this separation of concerns is to have a code editor as a form field and having the code editor validations reflect to the outside without exposing the code editor itself to the outside.
Upvotes: 0
Views: 1218
Reputation: 693
I know this is a bit late in the day, but I stumbled on this question while researching another issue and thought it would be worth adding an answer to help anyone else who reads this.
The issue is the way the error is being set inside the getter. At some point Angular will be reading the value of the property while inside a change detection loop, if you change the control errors at this point you will get the Expression Changed error.
The easy fix is to change slightly the way you are testing and setting the errors. In your stackblitz I changed the error state back to a boolean field and added the validation check to the DoCheck lifecycle hook.
ngDoCheck() {
const missmatch = this.innerForm.value.password !== this.innerForm.value.confirmPassword;
if (missmatch) {
this.ngControl.control.setErrors({ missmatch });
this.errorState = true;
return;
}
this.errorState = false;
}
Modified stackblitz with the fix
Upvotes: 1
Reputation: 353
testing your StackBlitz example i've seen that removing the *ngIf in app.component on the mat-error solve the problem keeping unchanged the "error reporting function" of form input, i don't know if this can solve your problem in your real site, but i think is worth a try.
But what i really suggest you for this kind of work (password match verification) is to rely on Angular form validators (here form documentation: https://angular.io/guide/form-validation) this approach is safer because you don't need to force the error checking via variables but is the form that alone understand if a field is invalid.
There is no standard validators for check if 2 fields contain the same text but you can build your custom validator that do what you want.
If this can be helpful i'll share the syntax of the custom validator that i usually use for password check (only as a trace, for sure you must modify something to make it work):
export function mismatchValidator(): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
if (control.parent) {
if (control.parent.get('password').value !== control.parent.get('password_confirmation').value) {
return {mismatch: true};
} else {
return null;
}
} else {
return null;
}
};
}
Other informations about how to implement form controller, validators and custom validators can be found on Angular site.
Here's an interesting article about your issue, maybe it can be useful to help you to understand where your problem is if you choose to not implement form Validators.
Upvotes: 1