Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
    }
}
like image 295
pasianos Avatar asked Jun 16 '16 08:06

pasianos


People also ask

Can we create component dynamically in Angular?

If we go by the Angular definition, a factory component Angular is a base class for a factory that can create a component dynamically. Instantiate a factory for a given type of component with resolveComponentFactory(). Use the resulting ComponentFactory. create() method to create a component of that type.

Which of the following is used when adding components dynamically in angular5?

Now Angular knows where to dynamically load components. The <ng-template> element is a good choice for dynamic components because it doesn't render any additional output.

What can I use instead of ComponentFactoryResolver?

In Angular 13 the new API removes the need for ComponentFactoryResolver being injected into the constructor, like you did in your code. Now to dynamically create a component you have to use ViewContainerRef.

Which method of ViewContainerRef create an instance of a component?

Descriptionlink. Can contain host views (created by instantiating a component with the createComponent() method), and embedded views (created by instantiating a TemplateRef with the createEmbeddedView() method).


1 Answers

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.

like image 157
Günter Zöchbauer Avatar answered Oct 24 '22 05:10

Günter Zöchbauer