ExpressionChangedAfterItHasBeenCheckedError when add validator in ngOnInit
Asked Answered
C

1

8

I am trying to add a custom validator to an input, but when I do so it triggers an ExpressionChangedAfterItHasBeenCheckedError error saying something changed from TRUE to FALSE.

I traced the problem down to the line below:

ngOnInit(): void {
    this.ipv4Fields.addControl('gateway', new FormControl('', [TabValidator.ipaddress()]));
}

If I remove the TabValidator.ipaddress() then the error disappears. Similarly if my validator forces return of 'null' then the error goes away. My validator is as follows:

private static _ipaddress(address: string): any {
    console.log('checking ip address: '+address+' valid: '+ip.isV4Format(address)+' is null: '+(address === null ? 'yes' : 'no'));
    if (!ip.isV4Format(address)) { return { 'wrongFormat': true }; }
    console.log('returning null');
    return null;
}
public static ipaddress(): ValidatorFn {
    return (control: AbstractControl): { [key: string]: any } => {
        console.log('validator returning: '+TabValidator._ipaddress(control.value));
        return TabValidator._ipaddress(control.value);
    };
}

From the console log it appears that the validator is returning the same value each time.

Can someone explain what is going wrong here? I can't explain what variable is changing from 'true' to 'false' since there are no booleans involved in the lines above. How can the validator return different values without appearing in my console log?

I read one SO questions about not doing things in ngOnInit but that seems like a red herring.


UPDATE: I tried:

ngOnInit(): void {
    Promise.resolve().then(() => {
    this.ipv4Fields.addControl('gateway', new FormControl('', [TabValidator.ipaddress()]));
    });
}

and also

ngOnInit(): void {
  setTimeout(() => this.ipv4Fields.addControl('gateway', new FormControl('', [TabValidator.ipaddress()])), 10);
}

but then error changes to:

ERROR Error: Cannot find control with name: 'gateway'


SOLUTION:

I traced this down to a bug - using reactive forms + validators + ngb tabs. Adding the validator to the control on a tab causes the error. Only solution is to change to template forms.

Callisto answered 7/9, 2017 at 16:30 Comment(4)
this mostly happens when something is not correct with the rendering order of your components. something in your lifecycle is out of order. there are a lot of threads on angular github issues page about this issue. review your components. verify if you don't have any *ngIf blocking the rendering of some component and so on...I ran into this problem several times and it was always because something was not correct with my code / components rendering order.Scherman
I don't have any ngIf or ngFor. I do apply different styles based on validity classes applied to the input.Callisto
yes, as I said, *ngIf* could be an issue, but there are a lot of things that cause that error and as I said, mostly because of the ordering of components rendering. There is too less code to tell you where is the issue. If you can reproduce in a plunker (I recommend nowadays stackblitz.com) would be very helpful...Scherman
check this article Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError errorHalsted
C
4

If you look at angular's github Issues page, you can find couple tickets related to same problem you have (as example https://github.com/angular/angular/issues/15634 , there are more, this one I just found as example). Long story short: angular doesn't like view change in some (all?) lifecycle hooks. What exactly changes in your particular case: I assume that is validity state of formField (i.e. internally in Angular's Control code, that you don't have access to), that internally triggers change detection...

If that is possible for you, move this.ipv4Fields.addControl() to constructor. If that is not possible - see suggestions in github tickets (do detectChanges, or Promise, or setTimeout, etc.)...

UPDATE:

@Component(){}
export class MyComponent implements OnInit{
    
    @Input()
    ipv4Fields: FormGroup;
    
    //inject `ChangeDetecorRef`
    constructor(protected changeDetectorRef: ChangeDetectorRef){
    }
    
    ngOnInit(){
        this.ipv4Fields.addControl('gateway', new FormControl('', [TabValidator.ipaddress()]));
        this.changeDetectorRef.detectChanges();
    }
}
Chengteh answered 7/9, 2017 at 17:13 Comment(6)
I tried to move to constructor but fields weren't created yet so it errored. I tried using promise (see question update above) but didn't work. Can you provide a small code example on what my ngOnInit should look like?Callisto
Personally, I'm so tired fighting with that error, so I each time I face that problem, I just do this.changeDetectorRef.detectChanges();. Each angular release they fix some of those problems, so I retest my code without that part. So controller(protected changeDetectorRef: ChangeDetectorRef){} ngOnInit(){/*other code;*/ this.changeDetectorRef.detectChanges();}. I had similar problem, when I need to update validator dynamically. My form was in modal. So instead of setting validator in ngOnInit I moved that code to modalShow() callback and that was enough %)Chengteh
Ok I implemented the above (I think controller should be constructor) but still get the ExpressionChanged... errorCallisto
sorry then, I don't have better solution now :(Chengteh
I appreciate the ideas. Does that mean my problem is different from the bug you mentioned, or that mine is just a different manifestation of the same bug? (guessing)Callisto
Hard to say, there might be problem with interaction between parent-child component, as example...Chengteh

© 2022 - 2024 — McMap. All rights reserved.