58

I want to create dynamic components and insert views of these components to a container.

I think this can be achieved by ViewContainerRef.

But I don't know, can we get ViewContainerRef of any component? if yes then how?. I am new to Angular, if there are any other good solutions available to handle this scenario, please suggest me.

Updated I think, I am pretty much near to the solution. Below is the code.

app.component.ts

import {Component} from '@angular/core';
import {ContainerComponet} from './container.component'

@Component({
    selector: 'my-app',
    template: `
    <container> </container>
    `,
    directives: [ContainerComponet]
})
export class AppComponent {

    constructor() { }

 }

container.component.ts

import {Component, ComponentResolver, ViewContainerRef} from '@angular/core'
import {controlBoxComponent as controlBox} from './controlBox.component';

@Component({
    selector: 'container',
    template: 'container'    
})
export class ContainerComponet {
    constructor(viewContainer: ViewContainerRef, private _cr: ComponentResolver) {

        this._cr.resolveComponent(controlBox)
            .then(cmpFactory => {
                const ctxInjector = viewContainer.injector;
                return viewContainer.createComponent(cmpFactory, 0,  ctxInjector);
            })

    }
}

controlBox.component.ts

import {Component} from '@angular/core'
@Component({
    selector: 'controlBox',
    template: 'controlBox'
})
export class controlBoxComponent {
    constructor() { }
}

Output

<my-app>
    <container>container</container><controlbox _ngcontent-lsn-3="">controlBox</controlbox>
</my-app>

Expected Result

<my-app>
    <container>container
    <controlbox _ngcontent-lsn-3="">controlBox</controlbox>
    </container>
</my-app>
Prem Parihar
  • 1,070
  • 2
  • 9
  • 15

4 Answers4

59

You can get the ViewContainerRef of the current component by or from an element in the view of the current component

@Component({
  selector: '...',
  directives: [OtherComponent, FooComponent],
  template: `
    <other-component></other-component>
    <foo-component #foo></foo-component>
    <div #div></div>`
})

export class SomeComponent {
  // `ViewContainerRef` from an element in the view
  @ViewChild(OtherComponent, {read: ViewContainerRef}) other;
  @ViewChild('foo', {read: ViewContainerRef}) foo;
  @ViewChild('div', {read: ViewContainerRef}) div;

  // `ViewContainerRef` from the component itself
  constructor(private viewContainerRef:ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver) {}

  let factory = this.componentFactoryResolver.resolveComponentFactory(ControlBox)
  this.componentRef = this.other.createComponent(factory);
  // this.componentRef = this.foo.createComponent(factory);
  // this.componentRef = this.div.createComponent(factory);
  // this.componentRef = this.viewContainerRef.createComponent(factory);
  });
}

See also Angular 2 dynamic tabs with user-click chosen components

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 4
    is there a possibility to add the dynamic created component as child of the viewcontainerref, as ex. as child of the ? Instead of creating it as neighbor in the DOM if you know what I mean. – NDY Jun 15 '16 at 19:34
  • 3
    No, you need a `ViewContainerRef` from inside that child to do that. – Günter Zöchbauer Jun 15 '16 at 19:37
  • 4
    @GünterZöchbauer pointed a way to insert in a child DOM in [this other SO question](http://stackoverflow.com/questions/38093727/angular2-insert-a-dynamic-component-as-child-of-a-container-in-the-dom): replace `
    ` by `` (and wrap with the tag you want). I think it worth adding this information to this answer, as it was a part of the initial question.
    – Antoine OL Jun 29 '16 at 10:58
0

I did something like that for my app. To load datas in a table.

To do that I've creat a directive :

directives: [TableDirective]

And then I use it like that :

@ViewChild(TableDirective) tableDirective:TableDirective;

ngAfterViewInit() {
    setTimeout(_=>this.load());
}

load() {
    this.tableDirective.loadTable(*ADirectiveToLoad*);
}

The TableDirective File :

import { Component, DynamicComponentLoader, ViewContainerRef } from 'angular2/core';

@Component({
    selector: "my-table",
    template: `<my-data></my-data>`
})

export class TableDirective {
    constructor(
        private dcl:DynamicComponentLoader,
        private viewContainerRef:ViewContainerRef) {
    }

public loadTable(base:any) {
    this.viewContainerRef.clear();
    this.dcl.loadNextToLocation(base, this.viewContainerRef);
}
}

This will load datas in my table, depend of the Directive I send. For exemple :

import { Component, OnInit } from 'angular2/core';

@Component({
    selector: "my-data",
    templateUrl: "app/_includes/table/actionnaire/table.html"
})

export class ActionnaireDirective implements OnInit {
    private entity:any;

ngOnInit() {
    this.entity = ACTIONNAIRES_PORTEUR;
}
}

var ACTIONNAIRES_PORTEUR:Actionnaire[] = [
    {"id": 1, "nom": "Test", "prenom": "Testeur", "dateNaissance": "15/05/1995"}
];

export class Actionnaire {
    id:number;
    nom:string;
    prenom:string;
    dateNaissance:any;
}

I'm also new with Angular :x

Aastal
  • 350
  • 2
  • 8
  • 2
    Thanks for the suggestion. [DynamicComponentLoader](https://angular.io/docs/ts/latest/api/core/index/DynamicComponentLoader-class.html) is marked as Deprecated, can we have other solution. – Prem Parihar May 12 '16 at 16:36
0

I was searching for a solution to this problem as well.

The only way I was able to make it happen was to use an additional Component

import {Component, ViewContainerRef} from '@angular/core';

@Component({
    selector: 'sw-view-container-ref',
    template: `<div></div>`
})

export class SwViewContainerRef {

    private _viewContainerRef:ViewContainerRef;

    constructor(viewContainerRef:ViewContainerRef) {
        this._viewContainerRef = viewContainerRef;
    }

    get viewContainerRef():ViewContainerRef {
        return this._viewContainerRef;
    }
}

container.component.ts

import {Component, ComponentResolver, ViewContainerRef, AfterViewInit, ViewChild,Injector} from '@angular/core'
import {controlBoxComponent as controlBox} from './controlBox.component';
import {SwViewContainerRef} from "./sw-view-container-ref";

@Component({
    selector: 'container',
    template: 'container<sw-view-container-ref #swViewContainerRef></sw-view-container-ref>',
    directives: [SwViewContainerRef]
})
export class ContainerComponet implements AfterViewInit {

    @ViewChild('swViewContainerRef', SwViewContainerRef) swViewChild:SwViewContainerRef;

    ngAfterViewInit() {
        this.desiredViewContainerRef = this.swViewChild.viewContainerRef;

        var self = this;

        this._cr.resolveComponent(controlBox).then((factory) => {

            var componentRef = self.desiredViewContainerRef.createComponent(factory, null, self.injector, null);
            componentRef.changeDetectorRef.detectChanges();
            componentRef.onDestroy(()=> {
                componentRef.changeDetectorRef.detach();
            })

            return componentRef;
        });
    }

    public desiredViewContainerRef:ViewContainerRef;

    constructor(private _cr: ComponentResolver, public injector:Injector) {

    }
}

It should produce something similar to this.

<my-app>
<container>container
<sw-view-container-ref><div></div></sw-view-container-ref>
<controlbox>controlBox</controlbox>
</container>
</my-app>

I'm not sure if my examples are clear or working, feel free to ask questions or make suggestions, I will try to answer and update my example.

Neoheurist
  • 3,183
  • 6
  • 37
  • 55
Sebastian
  • 159
  • 6
0

I struggled to find an up-to-date and simple answer on how to dynamically add a component in another component. Every answer, blog and article seems to be overcomplicated or based on componentFactoryResolver which is deprecated by now.

In its essence it only takes two lines of code and one line of html.

Only the parent component is included below, since the child component is composed completely as you would normally do. But there is one gotcha! to consider: If the child component is using ngOnChanges(), you will have to work around it, because ngOnChanges() is not fired in a dynamically inserted component.

parent-component.ts

import { Component, ComponentRef, ViewChild } from '@angular/core';

//The component to be insertet
import { ChildComponent } from './child.component';

@Component({
  selector: 'parent-component',
  templateUrl: './parent.component.html',
})

export class ParentComponent {

  constructor() { }

  childComp:ComponentRef<ChildComponent>;
  childCompInstance:any;

  @ViewChild('childContainer', { read: ViewContainerRef }) childContainer!: ViewContainerRef;

  ngAfterViewInit(): void {
    setTimeout(() => {
      //Note that childComponent is placed as a sibling to the childContainer in the dom. 
      this.childComp = this.childContainer.createComponent(childComponent);
      this.childCompInstance = this.childComp.instance;
     
      //childComponent has an @Output() event that we can subscribe to
      this.childCompInstance.someEvent.subscribe((event:boolean) => {
        //Whatever needs to be done...
      });
     
      //childComponent also has several @Input()'s
      this.childCompInstance.greeting = 'Hello';
      this.childCompInstance.recipient = 'world';
      
    });
  }
}

parent.component.html

<div class="parent-style">
  <!-- childComponent content is placed as a sibling to the viewContainer. --> 
  <!-- Therefore I use an ng-container to not have an empty element sitting around -->
  <ng-container #childContainer></ng-container>
</div>

Bonus info: You can move childContainer around in the DOM if needed (I did it by getting a handle of the parent div). You can even move it outside its parent DOM scope, and still have everything working as normal and controlled by parentComponent.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Jette
  • 2,459
  • 28
  • 37