This post has but one message for Angular users: Your nested form implementation may be better off with a ControlValueAccessor.
I am not the first one to do this. Also see this post by @julianobrasil. Nevertheless a disclaimer and a request: I've had about four weeks of professional Angular development experience, apply healthy skepticism with abundance. I'm writing to clarify my understanding, and give something to the community. If you think the claim is preposterous, reach out!
Now, let's get you convinced by building up a minimally realistic example.
Suppose we have a form that used to input how many customers we can accept in a given day. Initially, the form is very simple. There is one integer input field per day. The only validation requirement is that the capacity can't be smaller than 0 or larger than 20.
We implement this with one component that has a FormGroup property and bind the controls manually. This is the simplest use of Angular Reactive Forms. You can find a barebones example here.
Now let's imagine a more complex second iteration for the form. We need to be able to support varying capacity throughout the day. That is, instead of a single integer input, we need to allow the user to add/remove time periods, which are specified by a start time, an end time and a capacity. We need to make sure that user can't enter an end time that's earlier than the start time. And we need to make sure that the total capacity doesn't exceed 20 within a day.
Conceptually, we have two levels here: the time period level and the day level. We can implement two components, and nest the time period component in the day component. We can then reuse the time period component within and across days, and the day component across days.
Here's my solution, where each component implements ControlValueAccessor.
I think there are three key implementation details worth noting:
- Components that implement
ControlValueAccessor
interface bind toFormControl
s, and not to aFormGroup
orFormArray
. - You need to notify parents of value changes, just like the default value accessors for e.g. number inputs do.
ControlValueAccessor interface is very helpful here, especially while handling nested arrays that do not have a predetermined size. The interface is called a ControlValueAccessor for a reason ;) Don't forget the point 1 above.
There is one final implementation detail:
- You need to trigger change detection if a parent acts in a way that a child causes a change in the validity of the parent without triggering change detection automatically. This is the case e.g. when the parent triggers initialisation of a child which initializes in invalid state.
In my limited experience, this implementation is the most flexible and the most idiomatic one. Do you disagree? I'd be happy to learn from you!
Top comments (0)