Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QueryList not updated with dynamic component

Tags:

angular

I have a parent component which can have multiple instances of the same child component. The tricky part here is that an instance of the child component is available at init and based on certain logic within child component I am using a output event emitter to regenerate another child component from the parent component. However, when saving I just see the first instance of the child component which I had put in my html template and not the dynamically generated one. I did see other similar questions but none seem to had the same problem. What am I missing here?

Any help would be greatly appreciated.

UPDATE - start

Added a plunker to demonstrate the issue if someone wants to play around and find the cause

https://plnkr.co/edit/FW49ztzsg5uyXF7DtHDY?p=preview

UPDATE - end

Parent Component

import { Component, OnInit, ViewContainerRef, ViewChild, ComponentFactoryResolver, ViewChildren, QueryList } from '@angular/core';
import { ChildComponent } from './child/child.component';

@Component( {
    selector: 'parent-cmp',
    entryComponents: [ChildComponent],
    template: `
        <child-cmp (onSomeSelectionChange)="onSomeSelectionChange($event)"></child-cmp>
        <div #childCompSection></div>
        <button (click)="save()">Save</button>
    `
} )
export class ParentComponent implements OnInit {

    @ViewChild( 'childCompSection', { read: ViewContainerRef } ) childCompSection: any;
    currentComponentHolder: any;
    @ViewChildren( ChildComponent ) childComponents: QueryList<ChildComponent>;

    constructor( private resolver: ComponentFactoryResolver ) { }

    ngOnInit() {
        // do some stuff to load initial view, nothing done using childComponents
    }

    onSomeSelectionChange( someValue ) {
        if ( someValue !== 3 ) {
            let componentFactory = this.resolver.resolveComponentFactory( ChildComponent );
            this.currentComponentHolder = this.childCompSection.createComponent( componentFactory );
            this.currentComponentHolder.instance.onSomeSelectionChange.subscribe( data => this.onSomeSelectionChange( data ) );
        }
    }

    // Need all child components (which includes dynamically generated components) here 
    save() {
        console.log(this.childComponents); // This just prints the child-cmp object that is already added not the ones that are dynamically added via ComponentFactoryResolver
    }

}

Child Component

import { Component, Output, EventEmitter } from '@angular/core';
import { ChildComponent } from './child/child.component';

@Component( {
    selector: 'child-cmp',
    template: `
        <div>
            <select [(ngModel)]="selectedValue" (ngModelChange)="showNumberField($event)">
                <option value="0">Select...</option>
                <option value="1">Add...</option>
                <option value="2">Subtract...</option>
                <option selected value="3">End</option>
            </select>
            <div *ngIf="showField">
                <label>Number:</label><input type="number" [(ngModel)]="numValue">
            </div>
        </div>
    `
} )
export class ChildComponent {

    @Output() onSomeSelectionChange = new EventEmitter<Lookup>();
    selectedValue: number;
    numValue: number;
    showField: boolean = false;

    showNumberField(value) {
        if(value !== 3) {
            showField = true;
        }
        this.onSomeSelectionChange.emit( value ); // This does work fine and another instance of the child component is generated from parent component but QueryList in parent component does not get updated
    }
}
like image 473
Sayantan Avatar asked May 13 '17 00:05

Sayantan


2 Answers

I came across the same issue and found that you can subscribe to the query list changes which will fire if you add or remove an element. Hopefully will help someone else down the line...

@ViewChildren(MyComponent) myComp: QueryList<MyComponent>;

ngAfterViewInit() {
    this.myComp.changes
            .subscribe((queryChanges) => {
                // Do something
            });
}
like image 90
Joe Keene Avatar answered Oct 13 '22 00:10

Joe Keene


Got a confirmation from the Angular chat forum that @ViewChildren/@ViewChild can only read elements which are already available in the view. Since, dynamic components are not available during view init, I had to maintain a list of all such dynamically generated components to keep track of them.

like image 45
Sayantan Avatar answered Oct 12 '22 23:10

Sayantan