Search code examples
angulartypescripteventemitter

Angular - emit event from parent component to dynamic child components


EDIT: I've taken a different approach to solve this issue: retrieve dynamic child component values in the parent on save(), as per accepted answer.

I'm trying to have a parent component emit an event/observable so its children dynamic components trigger an action upon listening to such.

I've learnt around that is not possible to use @Input() nor @Output() decorators with dynamic components, so...

This is an outline of my use case:

  1. Dynamic child component consists of a group of 4 input elements. (done)
  2. Parent component has Add button that adds dynamic child component. This button can be used to add as many instances of the dynamic child component. (done)
  3. Dynamic child component has Delete button to remove its instance. (done)
  4. Parent component has property of type Array of objects. Each array item is an object containing the dynamic component input values. (to do)
  5. Parent component has Save button, which on click should emit the event to the dynamic component instances, so each can save its input values as an object. (i.e. 3 instances => [ {a:..., b:..., c:..., d:...}, {...}, {...} ]; 5 instances => 5 array items and so on). (to do)

I'm trying to emit the parent event from Save button (#5 above), and have each existing instance of the dynamic child component to listen to the event and then do a .push action on the parent array (#4 above).

Probably this is not best practice, but have not yet devised any other way to ensure that values are saved for the existing dynamic component instances after a potentially indeterminate, random number of add / remove instance actions have taken place.

Parent Component:

html

...
<button (click)="addDetailRow()" type="button">Add Detail Row</button>
<div #detailContainer></div>
...
<button (click)="save()">Save</button>
...

typescript

...
detailRowArray: Array<Object>;
...
addDetailRow() {
    let comp = this._cfr.resolveComponentFactory(DetailRowComponent);
    let detailRowComp = this.container.createComponent(comp);

    detailRowComp.instance._ref = detailRowComp;
    detailRowComp.instance.detailRowArray = this.detailRowArray;
}
save() {
    // TODO: here emit an event/observable
}
...

Dynamic Child Component:

html

<div><input type="text" name="a" [(ngModel)]="detail_item.a" #a></div>
<div><input type="text" name="b" [(ngModel)]="detail_item.b" #b></div>
<div><input type="text" name="c" [(ngModel)]="detail_item.c" #c></div>
<div>
  <input type="text" name="d" [(ngModel)]="detail_item.d" #d>
  <span>
    <button (click)="removeRow()">Remove Row</button>
  </span>
</div>

typescript

...
detail_item: { [key: string]: any; } = {};
detailRowArray: Array<Object>;
...
removeRow() {
  ...
}
...
// TODO: trigger this function when parent emits event/observable
saveToParentArray() {
     this.detailRowArray.push(this.detail_item);
}

P.S. Code base is using template driven form, so it is not possible to use the FormArray or so in it, (I'm just getting acquainted with angular 2+). Thanks for your attention.


Solution

  • Ok, I have taken a different resolution path:

    After some research on the options provided, I no longer looked to send event from parent to dynamic child component instances;

    instead, I grabbed the existing ViewContainerRefvariable, and using its API along a for loop I retrieved the dynamic component instances, from which in turn I retrieved the set of 4 input elements and then programmatically constructed the expected object, and pushed it as item to parent's detailRowArray array. Problem solved.

    Below a simplified version of the code:

      save() {
        // Temporary variables to construct array of arrays
        let parentNodeArray: any[] = [];
        let inputs: any[] = [];
        // loop to retrieve each existing instance of the dynamic component into temp variable
        for (let i = 0; i < this.container.length; i++) {
          let comp: any;
          comp = this.container.get(i);
          parentNodeArray.push(comp.rootNodes[0]);
    
        }
        // loop to retrieve values from the set of 4 input elements per instance
        // into temp array var (mixed with good ol' JS)
        parentNodeArray.forEach((elem: any) => {
          inputs.push( Array.prototype.slice.call( elem.querySelectorAll('input') ).map(node => {
            return node.value; });
          );
        });