Today I learned something insanely useful for working with ngForm
in Angular:ControlContainer
!
Let’s say you have a parent component with a <form>
that uses ngForm
(template-driven form validation) and a child component that is not by itself a ControlValueAccessor
(your child component does not provide ngModel
) but it contains one:
import { Component } from '@angular/core';
@Component({
selector: 'parent',
template: `
<form #myForm="ngForm">
<input type="text" [(ngModel)]="parentText" name="parentText" required />
<child></child>
<button type="submit" [disabled]="!myForm.form.valid">Submit</button>
</form>
`,
})
export class ParentComponent {
parentText = 'foo';
}
import { Component } from '@angular/core';
import { ControlContainer, NgForm } from '@angular/forms';
@Component({
selector: 'child',
template: `
<input type="text" [(ngModel)]="childText" name="childText" required />
`,
viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
})
export class ChildComponent {
childText: string;
}
Because the input field in child.component
has a required attribute it would be nice if the <button>
of parent.component
would be disabled as long as nothing was entered on the input field. But this doesn’t happen.
As you can see, Angular detects the missing value on the input field but it does not propagate the error to the form and therefore the button doesn’t get disabled.
As of yesterday, I thought my only option here was to make <child>
a ControlValueAccessor
itself and implement a validator for it. The validator would then look at the <input>
field and return an error if the input field is being detected as invalid. This is, of course, very labor-intensive and doesn’t spark joy at all.
Today I had the exact same problem but instead of just begrudgingly following the known path I thought “there must be a better way” and so I stumbled upon a StackOverflow question that got a great answer buried in a rather inconspicuous comment: Just add viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
to your child component. And what shall I say: It worked first try!
So what is ControlContainer?
The official Angular API documentation says it’s “A base class for directives that contain multiple registered instances of NgControl
. Only used by the forms module.”
So, in other words, it’s a container that contains multiple controls and it provides these controls to a form. Sounds exactly like the thing I searched for.
I really like how elegant and fast this solution is and will definitely use this more in the future.
I have been searching for this all morning and finally found this answer. Thank you for putting this together!
You’re welcome.
Nice!