angular
angular copied to clipboard
Problem with nested ControlValueAccessor and Validation
Which @angular/* package(s) are the source of the bug?
forms
Is this a regression?
No
Description
In Reactive Forms, if you have two or more nested custom form components that implement ControlValueAccessor and Validator, the outer one doesn't update its validation status until you make a change in the inner component.
It seems the validation runs initially only once from the outer component to the inner one, so if the inner component initially is invalid the outer doesn't know anything until the next validation cycle.
In the example provided, initially "user-data" is invalid, so "complex-form" should be also invalid, but as you can see it updates its status once you start typing in the input fields.
![status](https://user-images.githubusercontent.com/43267825/125175018-bd90ed80-e1c9-11eb-867d-7233349e3ef1.png)
Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/angular-ivy-nkr2be?file=src/app/complex-form.component.ts
Please provide the exception or error you saw
No response
Please provide the environment you discovered this bug in
Angular 12.0.5
Anything else?
No response
if you have two or more nested custom form components
I'm curious: what's the use case?
Imagine a huge form splitted in many subcomponents that are resused on other places of your application. For example, You could have a component responsible for editing a list of things, and that list could be part of a form with many more fields apart from that list. Each thing of that list could be another custom component, so you end up with a tree of form components.
That should be a reusable form group. I don't think it makes sense to put fields inside fields.
This is a subtle one. It will likely take a couple hours to really figure out why this is happening at initialization.
I prepared an example with FormGroups as @lazarljubenovic suggested, but the behaviour with wrong validation state in the root form on initialization/page refresh is the same as @GonzaloCorchon already pointed out: https://stackblitz.com/edit/angular-ivy-juakju
However, the issue only seems to be present, when the form nesting level is greater than one - as showcased in my example.
On both stackblitz above, it seems that calling updateValueAndValidity
in the CVA hierarchy's deepest component's ngAfterViewInit
lifecycle could be a temporary alternative.
is there any update on this? I don't think reusable formGroup is a valid solution here. It should be possible to have multiple levels of CVAs without any fancy logic behind it. And currently it works for all scenarios - except one - initialisation of all formControls. So update from bottom to top works - when user updates controls, everything bubbles up to the parent form. But if the child is invalid at the form creation, this calls validate() methods from top to bottom, and it doesn't update the parent components properly.
I made a repository to demonstrate the difference between nested CVA and FormGroups: https://github.com/Julien-Marcou/nested-cva-validation
The bug is still present in Angular v17.0.3
IMO it's still not a good idea to nest CVA inside each other, as it's not working properly with markAllAsTouched()
(https://github.com/angular/angular/issues/10887) and it's harder to control the state of sub control (enabled/disabled/touched/retrieve error of a specific control) and requires some hacky directive or other things to make it work properly.
But there might have some cases where it makes sense, so I think this validation bug is definitely something that should be looked into by the Angular team 🙏
What's really missing is a warning in the docs not to nest CVAs and the official guide for a reusable form group. Even better, a runtime warning.
It just doesn't make sense with the current semantics of @angular/forms
at all.
It should be possible to have multiple levels of CVAs (https://github.com/angular/angular/issues/42815#issuecomment-1284115159)
Why do you think that?
But there might have some cases where it makes sense
Got any examples? Even contrived ones?
I'm also running into this situation as well. My use case is that I have a CVA component comprised of several inputs for entering phone numbers. This phone component is then used to build another CVA that allows dynamically adding/removing these phone CVAs within a form array. Having these be separate components compartmentalizes the logic and gives more flexibility depending on the situation.
@dylhunn Do you have any updates on this issue? Or any recommendations for how we can deal with this issue in the meantime?