Context - I am currently working on developing a custom dropdown feature that can house various components. While I initially thought about using the <ng-content>
tag for this purpose, my team prefers a solution where the dropdown setup is done mainly through TypeScript code.
I considered using DynamicComponentLoader to achieve this, but most of the tutorials I came across referenced the loadIntoLocation() function, which is no longer available. Instead, I attempted to utilize the loadAsRoot() function, but encountered some challenges in making it work.
Here is my approach:
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;
}
}
However, I am encountering a runtime exception stating "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;
}
}