NgForm with nested validation

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.

child component that contains an input field which has the class ng-invalid
child.component has ng-invalid class. The button is enabled, nonetheless

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.

This one does not spark joy

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.

About the author

Domenic Helfenstein

2 comments

Recent Posts