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:
Add
button that adds dynamic child component. This button can be used to add as many instances of the dynamic child component. (done)Delete
button to remove its instance. (done)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.
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 ViewContainerRef
variable, 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; });
);
});