Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I dynamically inject an Angular2 sub component via typescript code?

Context - I'm trying to create a custom dropdown that can contain a number of components. I could accomplish this via the <ng-content> tag, but my team is stubbornly insisting that they don't like that. They want to be able to instantiate this dropdown almost entirely through typescript code.

I think I could accomplish this through the DynamicComponentLoader, but unfortunately, all the good tutorials I've found use the loadIntoLocation() function, which is now gone. So instead I've tried to use the loadAsRoot() function, but it's not working.

Here's what I'm trying to do:

Main.ts:

import { Component } from '@angular/core';
import { MyDropdown } from './MyDropdown';

@Component({
    selector: 'my-app',
    template: `
        <my-dropdown [contentModels]="dropdownContentModels"></my-dropdown>
    `
})
export class Main {
    dropdownContentModels: any[];
    constructor() {
        var someComponentModel = {selector: 'some-component', text: 'some'};
        var otherComponentModel = {selector: 'other-component', text: 'other'};
        this.dropdownContentModels = [someComponentModel, otherComponentModel];
    }
}

MyDropdown.ts:

import { Component } from '@angular/core';
import { InjectComponent } from './InjectComponent';

@Component({
    selector: 'my-dropdown',
    inputs: ['contentModels'],
    directives: [InjectComponent],
    template: `
        <div class="btn-group" dropdown>
            <button type="button" dropdownToggle>My Dropdown</button>
            <div class="dropdown-menu" role="menu">
                <inject-component *ngFor="let item of contentModels" [model]="item"></inject-component>
            </div>
        </div>
    `
})
export class MyDropdown {
    contentModels: any[];
}

InjectComponent.ts:

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

@Component({
    selector: 'inject-component',
    inputs: ['model'],
    template: `
        <div #toreplace></div>
    `,
    providers: [DynamicComponentLoader, Injector]
})
export class InjectComponent {
    model: any;
    constructor(private dcl: DynamicComponentLoader, private injector: Injector) {}
    ngOnInit() {
        this.dcl.loadAsRoot(this.createWrapper(), '#toreplace', this.injector);
    }
    createWrapper(): any {
        var model = this.model;
        @Component({
            selector: model.selector + '-wrapper',
            template: '<' + model.selector + ' [model]="model"></' + model.selector + '>'
        })
        class Wrapper {
            model: any = model;
        }

        return Wrapper;
    }
}

But I'm getting the runtime exception "EXCEPTION: Error: Uncaught (in promise): Can only add to a TokenMap! Token: Injector"

Update! (Thanks to echonax):

InjectComponent.ts:

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

@Component({
    selector: 'inject-component',
    inputs: ['model'],
    template: `
        <div #toreplace></div>
    `
})
export class InjectComponent {
    model: any;
    @ViewChild('toreplace', {read: ViewContainerRef}) toreplace;
    componentRef: ComponentRef<any>;

    constructor(private resolver: ComponentResolver) {}

    ngOnInit() {
        this.resolver.resolveComponent(this.createWrapper()).then((factory:ComponentFactory<any>) => {
            this.componentRef = this.toreplace.createComponent(factory);
        });
    }
    createWrapper(): any {
        var model = this.model;
        @Component({
            selector: model.selector + '-wrapper',
            directives: [ model.directives ],
            template: '<' + model.selector + ' [model]="model"></' + model.selector + '>'
        })
        class Wrapper {
            model: any = model;
        }

        return Wrapper;
    }
}
like image 922
SnoopDougg Avatar asked Jun 03 '16 15:06

SnoopDougg


1 Answers

You can use the new .createComponent() function.

import {ComponentRef, Injectable, Component, Injector, ViewContainerRef, ViewChild,ComponentResolver, DynamicComponentLoader} from '@angular/core';

export class InjectComponent {
    @ViewChild('toreplace', {read: ViewContainerRef}) toreplace;   


    constructor(private dcl: DynamicComponentLoader, injector: Injector,private resolver: ComponentResolver) {}

...

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

and remove providers: [DynamicComponentLoader, Injector]

Here's an example plunker that uses DynamicComponentLoader (in app.component.ts): https://plnkr.co/edit/azoGdAUvDvCwJ3RsPXD6?p=preview

like image 174
eko Avatar answered Sep 28 '22 09:09

eko