Search code examples
angularangular-forms

How to disable button in parent form when child form is invalid in Angular


I want to disable a button on the parent form when the nested child form is invalid.

This is the parent form component:

<hello name="{{ name }}"></hello>
<h2>Complex form with address component</h2>
<form #myForm="ngForm">
  <div>
    <label>Firstname:</label>
    <input type="text" name="firstName" ngModel>
  </div>
  <div>
    <label>Lastname:</label>
    <input type="text" name="lastName" ngModel>
  </div>
  <address></address>
</form>

<div>
  Root group valid: {{ myForm.valid }}
</div>
<br>

<!-- I want to disable this button --> 
<button [disabled]="!myForm.controls.address.valid">
  Submit Address Only
</button>

This is the child address form component:

import { Component } from '@angular/core';
import { ControlContainer, NgForm } from '@angular/forms';

@Component({
  selector: 'address',
  template: `
    <fieldset ngModelGroup="address" #group="ngModelGroup">
      <div>
        <label>Zip:</label>
        <input type="text" name="zip" ngModel>
      </div>
      <div>
        <label>Street:</label>
        <input type="text" name="street" ngModel>
      </div>
      <div>
        <label>City:</label>
        <input type="text" name="city" ngModel required>
      </div>
    </fieldset>
    Child group valid: {{ group.valid }}
  `,
  viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
})
export class AddressComponent  {}

I have attempted [disabled]="!myForm.controls.address.valid" but get an error stating that address is undefined

Here is a stackblitz https://stackblitz.com/edit/angular-jmdawu?file=app%2Fapp.component.html


Solution

  • You can work with the event emitter.

    Child Component

    TS

    // your instance of the FormGroup
    @ViewChild('group') group: FormGroup;
    
    // emits the formGroup state to the parent
    @Output() groupIsValid: EventEmitter<boolean> = new EventEmitter<boolean>();
    
    
    ngOnInit() {
        // subscribe to the state change event and emit the current state to the parent
        this.group.statusChanges().subscribe(() => {
            this.groupIsValid.emit(this.group.valid);
        });
    }
    

    Parent Component

    TS

    Here we define a method that gets called when the event occures and a variable, that holds the state.

    // initially set to false
    childComponentIsValid: false;
    
    // gets called by event emitter
    onChildComponentEvent(value: boolean): void {
        this.childComponentIsValid = value;
    
        console.log('current child component validity state is: ' + this.childComponentIsValid);
    }
    

    HTML

    Here we get the event from the child component by adding the parameter to the HTML-Tag

    <address (groupIsValid)="onChildComponentEvent($event)"></address>
    

    And here you use the variable.

    <button [disabled]="!childComponentIsValid">
        Submit Address Only
    </button>
    

    That's it.