pasianos
pasianos

Reputation: 75

Angular 2 dynamically add component instance into container

I'm trying to implement in Angular 2 a ComponentContainer so you can dynamically add other Components, similar to, for example, a LinearLayout in Android.

So far, using Angular 2 dynamic tabs with user-click chosen components I can dynamically add Components passing the Type.

My problem is that using this method, the created Component is created only by its Type and I don't know how to give arguments to it.

This is what i've done:

container.component.ts

import {
    Component,
    OnChanges,
    AfterViewInit,
    OnDestroy,
    Input,
    Type,
    ViewChild,
    ViewContainerRef,
    ComponentRef,
    ComponentResolver,
    ComponentFactory
} from '@angular/core';

@Component({
    selector: 'wrapper-component',
    template: '<div #container></div>'
})
class WrapperComponent implements OnChanges, AfterViewInit, OnDestroy {

    @Input() type: Type;
    @ViewChild('container', {read: ViewContainerRef}) container;

    cmpRef: ComponentRef<any>;

    private isViewInitialized: boolean = false;

    constructor(private resolver: ComponentResolver) { }

    private updateComponent() { 
        if(!this.isViewInitialized)
            return;

        if(this.cmpRef)
            this.cmpRef.destroy();

        this.resolver.resolveComponent(this.type).then((factory: ComponentFactory<any>) => {
            this.cmpRef = this.container.createComponent(factory);
        })
    }

    ngOnChanges() {
        this.updateComponent();
    }

    ngAfterViewInit() {
        this.isViewInitialized = true;
        this.updateComponent();
    }

    ngOnDestroy() {
        if(this.cmpRef)
            this.cmpRef.destroy();
    }
}

@Component({
    selector: 'container-component',
    template: `
        <wrapper-component 
            *ngFor="let element of elements" 
            [type]="element">
        </wrapper-component>
    `,
    directives: [WrapperComponent]
})
export class ContainerComponent {

    private elements: Type[] = [];    

    visibility: boolean = true;

    Add(element: Type) {
        this.elements.push(element);
    }

    AddAll(elements: Type[]) {
        elements.forEach(element => {
            this.Add(element);
        });
    }

    Clear() {
        this.elements = [];
    }
}

a1.component.ts

import { Component, Type, ViewChild } from '@angular/core'
import { ContainerComponent } from '../../components/container.component';

@Component({
    selector: 'child-component',
    template: '<div>a{{text}}</div>'
})
class ChildComponent {
    text: string;
}

@Component({
    selector: 'a1step',
    template: `
        <button (click)="onClick()">Add</button>
        <container-component #container></container-component>
    `,
    directives: [ContainerComponent]
})
export class A1Step {
    @ViewChild('container') container : ContainerComponent;

    onClick() {
        this.container.Add(ChildComponent);
    }
}

Here I can dynamically add ChildComponents, but how can I set its text?

EDIT:

I don't know if could be useful to others, but defining an argument class for the ChildComponent and updating the elements array of the ContainerComponent as an array of objects containing the specific args, I can easily pass specific arguments to the ChildComponents:

container.component.ts

import {
    Component,
    OnChanges,
    AfterViewInit,
    OnDestroy,
    Input,
    Type,
    ViewChild,
    ViewContainerRef,
    ComponentRef,
    ComponentResolver,
    ComponentFactory
} from '@angular/core';

export class ChildArgs {
    type: Type;
    args: any;
}

@Component({
    selector: 'wrapper-component',
    template: '<div #container></div>'
})
class WrapperComponent implements OnChanges, AfterViewInit, OnDestroy {

    @Input() argsElement: ChildArgs;
    @ViewChild('container', {read: ViewContainerRef}) container;

    cmpRef: ComponentRef<any>;

    private isViewInitialized: boolean = false;

    constructor(private resolver: ComponentResolver) { }

    private updateComponent() { 
        if(!this.isViewInitialized)
            return;

        if(this.cmpRef)
            this.cmpRef.destroy();

        this.resolver.resolveComponent(this.argsElement.type).then((factory: ComponentFactory<any>) => {
            this.cmpRef = this.container.createComponent(factory);
            this.cmpRef.instance.args = this.argsElement.args;
        })
    }

    ngOnChanges() {
        this.updateComponent();
    }

    ngAfterViewInit() {
        this.isViewInitialized = true;
        this.updateComponent();
    }

    ngOnDestroy() {
        if(this.cmpRef)
            this.cmpRef.destroy();
    }
}

@Component({
    selector: 'container-component',
    template: `
        <wrapper-component 
            *ngFor="let argsElement of argsElements" 
            [argsElement]="argsElement">
        </wrapper-component>
    `,
    directives: [WrapperComponent]
})
export class ContainerComponent {

    private argsElements: ChildArgs[] = [];

    AddArgsElement(argsElement: ChildArgs) {
        this.argsElements.push(argsElement);
    }
}

a1.component.ts

import { Component, Type, ViewChild } from '@angular/core'
import { ContainerComponent, ChildArgs } from '../../components/container.component';

class ChildComponentArgs {
    text: string;
}

@Component({
    selector: 'child-component',
    template: '<div>a{{args.text}}</div>'
})
class ChildComponent {
    args: ChildComponentArgs;
}

class ChildComponent2Args {
    text: string;
}

@Component({
    selector: 'child-component2',
    template: '<div>b{{args.text}}</div>'
})
class ChildComponent2 {
    args: ChildComponent2Args;
}

@Component({
    selector: 'a1step',
    template: `
        <button (click)="onClick()">Add</button>
        <button (click)="onClick2()">Add2</button>
        <container-component #container></container-component>
    `,
    directives: [ContainerComponent]
})
export class A1Step {
    @ViewChild('container') container : ContainerComponent;

    private cnt: number = 0;
    private cnt2: number = 0;

    onClick() {
        let childComponentArgs: ChildComponentArgs = new ChildComponentArgs();
        childComponentArgs.text = "" + ++this.cnt; 

        let childArgs: ChildArgs = new ChildArgs();
        childArgs.type = ChildComponent;
        childArgs.args = childComponentArgs;

        this.container.AddArgsElement(childArgs);
    }

    onClick2() {
        let childComponentArgs: ChildComponent2Args = new ChildComponent2Args();
        childComponentArgs.text = "" + ++this.cnt2; 

        let childArgs: ChildArgs = new ChildArgs();
        childArgs.type = ChildComponent2;
        childArgs.args = childComponentArgs;

        this.container.AddArgsElement(childArgs);
    }
}

Upvotes: 5

Views: 11872

Answers (1)

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657108

You can access the created component instance using cmpRef.instance:

   this.resolver.resolveComponent(this.type).then((factory: ComponentFactory<any>) => {
        this.cmpRef = this.container.createComponent(factory);
        this.cmpRef.instance.text = this.someText;
    })

See also Angular 2 dynamic tabs with user-click chosen components for a full example.

Upvotes: 5

Related Questions