How can I pass the FormGroup of a parent component to its child component using the current Form API

55,918

Solution 1

In the parent component do this:

<div [formGroup]="form">
  <div>Your parent controls here</div>
  <your-child-component [formGroup]="form"></your-child-component>
</div>

And then in your child component you can get hold of that reference like so:

export class YourChildComponent implements OnInit {
  public form: FormGroup;

  // Let Angular inject the control container
  constructor(private controlContainer: ControlContainer) { }

  ngOnInit() {
    // Set our form property to the parent control
    // (i.e. FormGroup) that was passed to us, so that our
    // view can data bind to it
    this.form = <FormGroup>this.controlContainer.control;
  }
}

You can even ensure either formGroupName or [formGroup] is specified on your component by changing its selector like so:

selector: '[formGroup] epimss-error-messages,[formGroupName] epimss-error-messages'

This answer should be sufficient for your needs, but if you want to know more I've written a blog entry here:

https://mrpmorris.blogspot.co.uk/2017/08/angular-composite-controls-formgroup-formgroupname-reactiveforms.html

Solution 2

this is an example of child component used inside parent formGroup : child component ts:

import { Component, OnInit, Input } from '@angular/core';
import { FormGroup, ControlContainer, FormControl } from '@angular/forms';


@Component({
  selector: 'app-date-picker',
  template: `
  <mat-form-field [formGroup]="form" style="width:100%;">
  <input matInput [matDatepicker]="picker" [placeholder]="placeHolder" [formControl]="control" readonly>
  <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
  <mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<mat-icon (click)="clearDate()">replay</mat-icon>`,
  styleUrls: ['./date-picker.component.scss']
})

export class DatePickerComponent implements OnInit {
  public form: FormGroup;
  public control : FormControl;
  @Input() controlName : string;
  @Input() placeHolder : string;


  constructor(private controlContainer: ControlContainer) { 
  }

  clearDate(){
    this.control.reset();
  }

  ngOnInit() {
    this.form = <FormGroup>this.controlContainer.control;
    this.control = <FormControl>this.form.get(this.controlName);
    }

}

css date picker :

mat-icon{
position: absolute;
left: 83%;
top: 31%;
transform: scale(0.9);
cursor: pointer;
}

and used like this :

 <app-date-picker class="col-md-4" [formGroup]="feuilleForm" controlName="dateCreation" placeHolder="Date de création"></app-date-picker>

Solution 3

Parent Component :

    @Component({
      selector: 'app-arent',
      templateUrl: `<form [formGroup]="parentFormGroup" #formDir="ngForm">
                       <app-child [formGroup]="parentFormGroup"></app-child>
                    </form>         `
    })
    
    export class ParentComponent implements {
        
     parentFormGroup :formGroup
    
     ngOnChanges() {        
       console.log(this.parentFormGroup.value['name'])
     }
  }

Child Component :

    @Component({
      selector: 'app-Child',
      templateUrl: `<form [formGroup]="childFormGroup" #formDir="ngForm">
                        <input id="nameTxt" formControlName="name">
                    </form>         `
    })
    
    export class ChildComponent implements OnInit {
     @Input()  formGroup: FormGroup
    
     childFormGroup :FormGroup
    
    ngOnInit() {
      // Build your child from
      this.childFormGroup.addControl('name', new FormControl(''))
    
      /* Bind your child form control to parent form group
         changes in 'nameTxt' directly reflect to your parent 
         component formGroup
        */          
     this.formGroup.addControl("name", this.childFormGroup.controls.name);
   
     }
  }

Solution 4

For Angular 11 I tried all the above answers, and in different combinations, but nothing quite worked for me. So I ended up with the following solution which worked for me just as I wanted.

TypeScript

@Component({
  selector: 'fancy-input',
  templateUrl: './fancy-input.component.html',
  styleUrls: ['./fancy-input.component.scss']
})
export class FancyInputComponent implements OnInit {

  valueFormGroup?: FormGroup;
  valueFormControl?: FormControl;

  constructor(
    private formGroupDirective: FormGroupDirective, 
    private formControlNameDirective: FormControlName
  ) {}

  ngOnInit() {
    this.valueFormGroup = this.formGroupDirective.form;
    this.valueFormControl = this.formGroupDirective.getControl(this.formControlNameDirective);
  }

  get controlName() {
    return this.formControlNameDirective.name;
  }

  get enabled() {
    return this.valueFormControl?.enabled
  }

}

HTML

<div *ngIf="valueFormGroup && valueFormControl">
    <!-- Edit -->
    <div *ngIf="enabled; else notEnabled" [formGroup]="valueFormGroup">
        <input class="input" type="text" [formControlName]="controlName">        
    </div>
    <!-- View only -->
    <ng-template #notEnabled>
        <div>
            {{valueFormControl?.value}}
        </div>
    </ng-template>
</div>

Usage

Note that I had to add ngDefaultControl otherwise it would give no default value accessor error in console (if somebody knows how to get rid of it without error - will be much appreciated).

<form [formGroup]="yourFormGroup" (ngSubmit)="save()">
    <fancy-input formControlName="yourFormControlName" ngDefaultControl></fancy-input>
</form>
Share:
55,918
st_clair_clarke
Author by

st_clair_clarke

Updated on August 13, 2021

Comments

  • st_clair_clarke
    st_clair_clarke almost 3 years

    I would like to pass the parent component's FormGroup to its child for the purpose of displaying an error-message using the child.

    Given the following parent:

    parent.component.ts

    import { Component, OnInit } from '@angular/core'
    import {
      REACTIVE_FORM_DIRECTIVES, AbstractControl, FormBuilder, FormControl, FormGroup, Validators
    } from '@angular/forms'
    
    @Component({
      moduleId: module.id,
      selector: 'parent-cmp',
      templateUrl: 'language.component.html',
      styleUrls: ['language.component.css'],
      directives: [ErrorMessagesComponent]
    })
    export class ParentCmp implements OnInit {
      form: FormGroup;
      first: AbstractControl;
      second: AbstractControl;
      
      constructor(private _fb: FormBuilder) {
        this.first = new FormControl('');
        this.second = new FormControl('')
      }
      
      ngOnInit() {
        this.form = this._fb.group({
          'first': this.first,
          'second': this.second
        });
      }
    }
    

    I would now like to pass the form:FormGroup variable above to the child component below:

    error-message.component.ts

    import { Component, OnInit, Input } from '@angular/core'
    import { NgIf } from '@angular/common'
    import {REACTIVE_FORM_DIRECTIVES, FormGroup } from '@angular/forms'
    
    @Component({
      moduleId: module.id,
      selector: 'epimss-error-messages',
      template: `<span class="error" *ngIf="errorMessage !== null">{{errorMessage}}</span>`,
      styles: [],
      directives: [REACTIVE_FORM_DIRECTIVES, NgIf]  
    })
    export class ErrorMessagesComponent implements OnInit {
      @Input() ctrlName: string
    
      constructor(private _form: FormGroup) { }
    
      ngOnInit() { }
    
      get errorMessage() {
        // Find the control in the Host (Parent) form
        let ctrl = this._form.find(this.ctrlName);
        console.log('ctrl| ', ctrl);
    
    //    for (let propertyName of ctrl.errors) {
    //      // If control has a error
    //      if (ctrl.errors.hasOwnProperty(propertyName) && ctrl.touched) {
    //        // Return the appropriate error message from the Validation Service
    //        return CustomValidators.getValidatorErrorMessage(propertyName);
    //      }
    //    }
    
        return null;
      }
    

    The constructor formGroup represents the FormGroup of the parent - in its present form it does not work.

    I am trying to follow this obsolete example at http://iterity.io/2016/05/01/angular/angular-2-forms-and-advanced-custom-validation/