Search code examples
angularangular-reactive-formsangular-directiveangular-components

Angular - Call child component function from parent class


I have a parents class integrated with two child templates (3 separate form details in one). Sample form format as below:

Parent Form

  • Child A Form (Add Child A Details) (Delete Child A Details)
  • Child B Form (Add Child B Details) (Delete Child B Details)

Add Parent Form.

In the above sample, I was able to successfully duplicate the form elements using formarray and formbuilder. However when I click the "Add Child A Details" it should duplicate the form elements under Child B form, but in the current design it is duplicated before child B.

To meet the above requirement I tried to add the "Add" buttons of both the child functionality in the parent form and try to interact with the child class by using Input Decorator. However I'm not sure how to send a click event to the child form to trigger the function of add in the child from the parents class.

Parent form:

<app-child-A [addDetail()]></app-child-A>

<div class="form-group row mb-2">
      <div class="col-md-4">
        <button class="btn btn-outline-primary"
                type="button" style="margin-left:10px; font-size: 12px;"
                (click)="addDetail()">
          Add Details
        </button>
      </div>
    </div>

Child A:

eventForm: FormGroup;
constructor(private fb: FormBuilder) { }

 ngOnInit(): void {
    this.eventForm=this.fb.group({
      eventDetails: this.fb.array([this.buildDetail()])
    }); 
  }
  get eventDetails(): FormArray {
    return this.travelForm.get('travellerDetails') as FormArray;
  }
 @Input() addDetail(): void {
    this.eventDetails.push(this.buildDetail());
  }
 buildDetail(): FormGroup {
    return this.fb.group({
      eventName: '',
      desc: '',
      start: '',
      reach: '',
      transport: '',
      cost:null
    });
  }

I'm new to Angular please help, also if there are any other way of implementing the requirement instead of Input decorators please let me know.

Thank you!


Solution

  • We can achieve component interaction in angular in multiple ways.

    1. Using @Input[property Binding], @Output(Event Emission)
    2. Using Rxjs Observable/Subjects
    3. Using Local/Session/Cache Storage.

    I hope this Demo applications fulfil your requirement.

    In the above demo, i have created a shared service as below

    import { Injectable } from "@angular/core";
    import {Observable, BehaviorSubject } from 'rxjs';
    
    @Injectable({
      providedIn: 'root'
    })
    export class AppService {
      public eventFormChange = new BehaviorSubject<any>(null);
    
      triggerFormChange(data: any) {
        this.eventFormChange.next(data);
      }
    
      formChangeEvent() {
        return this.eventFormChange.asObservable();
      }
    }
    

    the parent component(App component)feeds updated form to the Shared Service as below.

    import { Component } from '@angular/core';
    import{FormGroup, FormBuilder, FormArray} from '@angular/forms';
    import { AppService } from './app.service';
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      eventForm: FormGroup;
    constructor(private fb: FormBuilder,
    private appService:AppService
    ) { }
    
     ngOnInit(): void {
        this.eventForm=this.fb.group({
          eventDetails: this.fb.array([this.buildDetail()])
        }); 
        this.updateForm();
      }
    
    get eventDetails(): FormArray {
        return this.eventForm.get('eventDetails') as FormArray;
      }
    
      buildDetail(): FormGroup {
        return this.fb.group({
          eventName: '',
          desc: '',
          start: '',
          reach: '',
          transport: '',
          cost:null
        });
      }
    
    addDetail(): void {
        this.eventDetails.push(this.buildDetail());
        this.updateForm();
      }
    
      updateForm(){
        this.appService.triggerFormChange(this.eventForm);
      }
    }
    

    finally Child components will subscribe to the formChangeEvent() of shared service and rebuilds the form again as below.

    import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core';
    import { FormGroup, FormArray } from '@angular/forms';
    import { AppService } from '../app.service';
    
    @Component({
      selector: 'child-component',
      templateUrl: './child.component.html',
      styleUrls: ['./child.component.css'],
    })
    
    export class ChildComponent implements OnInit {
      eventDetails: FormArray;
      formInfo: FormGroup;
      constructor(
        private appService: AppService
      ) { }
      ngOnInit() {
        this.appService.formChangeEvent().subscribe(res => {
          console.log(res);
          this.formInfo = res;
          this.eventDetails = this.formInfo.get('eventDetails') as FormArray;
        });
      }
    

    }

    parent component(app.component.html)

    <div class="container">
    <div>Child Component 1
    </div>
    <hr>
    
    <child-component></child-component>
    
    <div>Child Component 2
    </div>
    <hr>
    
    <child-component></child-component>
    <button class="btn btn-outline-primary"
                type="button" style="margin-left:10px; font-size: 12px;"
                (click)="addDetail()">
          Add Details
        </button>
    

    child.component.html

    <form *ngIf="!!formInfo" [formGroup]="formInfo">
    <ng-container formArrayName="eventDetails">
        <div *ngFor="let _ of eventDetails.controls; index as i">
            <div class="item row" [formGroupName]="i">
                <div class="col-sm-6">
                    <div class="form-group">
                        <label for="eventName" class="col-form-label">Event Name</label>
                        <div>
                            <input type="text" class="form-control" id="eventName" formControlName="eventName">
    </div>
                        </div>
                    </div>
                    <div class="col-sm-6">
                        <div class="form-group">
                            <label for="desc" class="col-form-label">Description</label>
                            <div>
                                <input type="text" class="form-control" id="desc" formControlName="desc">
    </div>
                            </div>
                        </div>
                    </div>
                </div>
    </ng-container>