1
votes

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.

3
You can have inputs/outputs on your dynamic components, you just have to wire them up yourself. It would be best for your dynamic components to make use of outputs (public properties that are EventEmitters) to broadcast changes to the parent. The parent should know about the children but not vice-versa. - Daniel W Strimpel
Thanks @DanielWStrimpel. Any hint on how to wire Input() or Output() to a dynamic component? - j4v1

3 Answers

1
votes

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; });
      );
    });
0
votes

Method 1 :

The data array can exist in parent and child components accept that as input and modify it.

Method 2 :

Ideally (as mentioned in the comments already) you should do it the other way. We can trigger an event from child to the parent when user enters data into the child component

Method 3:

But if you still want to perform something in the child component from parent, you can just call the child component instance and call them.

detailRowComp[1].instance.saveToParentArray()

But this is redundant , not a good practice. Choose one of the above two.

0
votes

Use Objects instead of number or string variables to update child component with dynamic values.