Dynamic Component Creation in Angular 2
Edit Revised in Dynamic Component Creation in Angular 2 RC.5
Since Angular 2, $compile
was dropped.
There are no ways to insert HTML fragments into the component view except using innerHTML
.
But if we could create a component and load it…? Yes, we can do it!
This post will explain about dynamic HTML projection in Angular 2 with dynamic component creation.
Angular version: 2.0.0-rc.1
Component
as a function
We usually use a decorator, @Component
, to create a component class like the following:
@Component({
selector: 'static-component',
template: `<p>Static Component</p>`
})
class StaticComponent {
}
Actually, above component is almost equivalent to the next:
class DynamicComponent {
}
const dynamicComponent = Component({
selector: 'dynamic-component',
template: `<p>Dynamic Component</p>`
})(DynamicComponent);
Decorator isn’t a magic. It’s just function-calling.
Dynamic Component Loading
We can already use ViewContainerRef
and ComponentResolver
to load components into the host component dynamically.
ViewContainerRef
ViewContainerRef
is a container where one or more views can be attached.
It’s used in many places; NgIf
, NgFor
, RouterOutlet
.
Creation types by ViewContainerRef
are two pattern.
One is loading into next location. A component is loaded into the next location of the container.
Result:
<container-directive></container-directive>
<loaded-directive></loaded-directive>
Another is loading as embedded view. It needs TemplateRef
.
Result:
<container-directive>
<loaded-directive></loaded-directive>
</container-directive>
ComponentResolver
ComponentResolver
is a service to create ComponentFactory
from a component type.
All components are loaded into the view by ComponentFactory
.
ComponentResolver
has a method, resolveComponent
, which return a promise of ComponentFactory
.
Dynamic Component Factory
So, we can create any components in dynamically by using above features. Let’s look at the following code:
export function createComponentFactory(resolver: ComponentResolver, metadata: ComponentMetadata): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);
return resolver.resolveComponent(decoratedCmp);
}
createComponentFactory
returns a promise of ComponentFactory
created dynamically. It’s very simple.
Next, use the function at new DynamicHTMLOutlet
directive.
@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, []);
});
}
}
Last, let’s use the outlet.
@Component({
selector: 'my-app',
template: `
<dynamic-html-outlet [src]="html"></dynamic-html-outlet>
`,
directives: [DynamicHTMLOutlet]
})
export class MyApp {
html = `<div>
<p>Dynamic HTML Fragment</p>
</div>`;
}
Plunker is here
Summary
- Decorators are just function
- You can create a component by using
Component
andComponentResolver
- You can load a component by using
ViewContainerRef
andComponentFactory
