Dynamic Component Creation in Angular 2 RC.5
Few months ago, I wrote an article, Dynamic Component Creation in Angular 2. It said how to load a HTML template with using dynamic component creation.
Now, Angular 2 RC.5 has released! Some APIs were deprecated and some introduced. Let’s revise my dynamic component creation in latest Angular 2.
Angular version: 2.0.0-rc.5
ComponentOutlet
directive
Previously, I declared DynamicHTMLOutlet
;
@Directive({
selector: 'dynamic-html-outlet',
})
export class DynamicHTMLOutlet {
@Input() src: string;
constructor(private vcRef: ViewContainerRef, private resolver: ComponentResolver) {
}
ngOnChanges() {
if (!this.src) return;
const metadata = new ComponentMetadata({
selector: 'dynamic-html',
template: this.src,
});
createComponentFactory(this.resolver, metadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.createComponent(factory, 0, injector, []);
});
}
}
My new idea is ComponentOutlet
, it has own template, selector and context.
By context passing, we can use data-binding and event-handling in the dynamic template.
@Directive({
selector: '[componentOutlet]',
})
export class ComponentOutlet {
@Input('componentOutlet') private template: string;
@Input('componentOutletSelector') private selector: string;
@Input('componentOutletContext') private context: Object;
private _createDynamicComponent() {
this.context = this.context || {};
const metadata = new ComponentMetadata({
selector: this.selector,
template: this.template,
});
const cmpClass = class _ { };
cmpClass.prototype = this.context;
return Component(metadata)(cmpClass);
}
}
And it’s used with templating syntax (*
-prefix) like *ngIf
.
Templating syntax can turn the element into the template which doesn’t appear in the DOM.
So, we can replace an element has componentOutlet
with the dynamic component.
@Component({
selector: 'my-app',
template: `
<div *componentOutlet="html; context:self; selector:'my-dynamic-component'"></div>
`,
})
export class App {
self = this; // copy of context
html = `
<div>
<button (click)="self.showAlert('dynamic component')">Click</button>
</div>`;
setMessage(message: string) {
alert(message);
}
}
It turns into like below;
<my-app>
<my-dynamic-component>
<div>
<button>Click<button>
</div>
<my-dynamic-component>
</my-app>
This is what I really wanted. don’t you?
Use Compiler
In RC.5, ComponentResolver
is deprecated. Instead, We can use Compiler
API to create ComponentFactory
.
Before:
this.resolver.resolveComponent(this._createDynamicComponent())
.then(factory => {
...
})
After:
this.compiler.compileComponentAsync(this._createDynamicComponent())
.then(factory => {
...
});
Compiler
belongs to the application module.
So the compiler can use all directives and pipes which is in declarations
of the module.
At the final, ComponentOutlet
code is following:
@Directive({
selector: '[componentOutlet]',
})
export class ComponentOutlet {
@Input('componentOutlet') private template: string;
@Input('componentOutletSelector') private selector: string;
@Input('componentOutletContext') private context: Object;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }
private _createDynamicComponent() {
this.context = this.context || {};
const metadata = new ComponentMetadata({
selector: this.selector,
template: this.template,
});
const cmpClass = class _ { };
cmpClass.prototype = this.context;
return Component(metadata)(cmpClass);
}
ngOnChanges() {
if (!this.template) return;
this.compiler.compileComponentAsync(this._createDynamicComponent())
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.vcRef.clear();
this.vcRef.createComponent(factory, 0, injector);
});
}
}
Let’s use the outlet.
@Component({
selector: 'my-app',
template: `
<p>{{message}}</p>
<div *componentOutlet="html; context:self; selector:'my-dynamic-component'"></div>
`,
})
export class App {
message = 'static component';
self = this; // copy of context
html = `
<div>
<button (click)="self.setMessage('dynamic component')">Click</button>
</div>`;
setMessage(message: string) {
this.message = message;
}
}
@NgModule({
imports: [BrowserModule],
declarations: [App, ComponentOutlet],
bootstrap: [App]
})
export class AppModule {}
Summary
ComponentOutlet
with*
-prefix syntaxNgModule
andCompiler
If you want to use my ComponentOutlet
, you can install angular2-component-outlet
package.
laco0416/angular2-component-outlet: Angular2 dynamic component outlet
npm install --save angular2-component-outlet
Please give me feedback!
