简体   繁体   中英

Angular Reactive Form - setErrors not updating view for FormControls inside FormArray when OnPush is used

It is an interesting behaviour I tried in multiple projects and gave up using OnPush for FormArray, more specific it is an arry of row of FormGrop contains FormControl . However, I think it is time to raise a question here so that it maybe same issue for others (or just me too stupid that forget a simple tiny line of code, please point it out if so, thanks), I created a mockup here: stackblitz

For some reason my project using OnPush for all such dummy componnents which needs manually call ChangeDetectionRef.markForCheck if a View Update is required. But this time wherever I put the mark it doesn't show any differece.

Generally, I created a save$ Subject to do a validation on the fly and save the whole Grid to somewhere if it is valid . Now, since save$ is a BehaviourSubject which means it is called once the form is built. You can see the whole FormGroup is invalid on the screen but all cells are valid , which is weird. 在此处输入图像描述

The reason I mentioned about "non onpush" is because with that mode, Angular runs CD for you. And if your code works as expected in default mode, it means, your "onpush" code just needed to trigger CD.
I didn't mean to tell you that you should switch to default mode.

For why it didn't work in your original question:
The form itself, by default run updateValueAndValidity after initialized, you don't declare any validators => the form status is valid; then you call setErrors which behind the scene, set the from status to false => this cause the inconsistency in the view, hence the ng100 error.

//Edit:
I debugged the flow again and found out that after each control gets added to the form, it will run the updateValueAndValidity function again. Which means, the setErrors will cause the form to be Invalid first, then updateValueAndValidity will make it all Valid again. The inconsistency is still there, just the other way around.
//End Edit

The dirty hack for this is using setTimeout, which I personally don't like and don't recommend. But to give a properly solution, it will require to know the business logic in the code.

Here it is the code I posted in the comment.

setTimeout(() => {
        control.setErrors(Object.keys(errors).length ? errors : null);        
});

This helps to schedule the setError (which actually, the valid status) to run in the next CD round.
This code doesn't have any CD yet. So yes, the form is valid all the time => That's the reason I tell you to create a button to see the CD in action.

Now we wouldn't want a new button for users to click to see the form update. We need to run the CD in code. So where to put it?
We know the setError trigger the form status so we will schedule it later, and where does the setError get invoked? customValidator , we will schedule this function to run later so the final code to modify is here:

//instead of run setTimeout in a loop
//we make sure that all setError is scheduled in one run
setTimeout(() => {
      customValidator()(form);
      this.cd.markForCheck();
    })

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM