Dynamic Create Angular Views
There are two kinds of view in angular: embedded view and host view.
Embedded View
Embedded view create from a template, can not directly operate on template, but template ref instead.
First define template using ng-template
tag embedded in component template string or file:
@Component({
selector: 'sample',
template: `
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
Then get template ref:
export class SampleComponent implements AfterViewInit {
@ViewChild("tpl") tpl: TemplateRef<any>;
Create:
ngAfterViewInit() {
let elementRef = this.tpl.elementRef;
// outputs `template bindings={}`
console.log(elementRef.nativeElement.textContent);
}
}
Put them together:
@Component({
selector: 'sample',
template: `
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tpl") tpl: TemplateRef<any>;
ngAfterViewInit() {
let elementRef = this.tpl.elementRef;
// outputs `template bindings={}`
console.log(elementRef.nativeElement.textContent);
}
}
Host view
Only entry component can dynamically created, because component may use DI, an instance of Injector needed to do the DI job. Fortunately each component instance bound with an injector, we can get it from ID system.
Here is the sample code:
constructor(private injector: Injector,
private r: ComponentFactoryResolver) {
// resolves component factory, btw factory is generated by angular
// compiler for each our component.
let factory = this.r.resolveComponentFactory(ColorComponent);
// create component view, and returns newly created ref of the
// component.
let componentRef = factory.create(injector);
// get the hosted view.
let view = componentRef.hostView;
}
Add view to UI
A view itself is useless if not add to component tree, we need view container.
View container is defined using a special tag ng-container
like:
<span>I am first span</span>
<ng-container #vc></ng-container>
<span>I am last span</span>
At runtime, <ng-container>
rendered as html comment, it is nothing more than a placeholder.
Again, we can not get ViewContainer
but ViewContainerRef
:
export class SampleComponent implements AfterViewInit {
@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;
Using ViewContainer
operate on views:
class ViewContainerRef {
...
clear() : void
insert(viewRef: ViewRef, index?: number) : ViewRef
get(index: number) : ViewRef
indexOf(viewRef: ViewRef) : number
detach(index?: number) : ViewRef
move(viewRef: ViewRef, currentIndex: number) : ViewRef
}
As you can see, not just insert views at the placeholder, but do almost anything. Now back to business, insert our dynamic created views:
@Component({
selector: 'sample',
template: `
<span>I am first span</span>
<ng-container #vc></ng-container>
<span>I am last span</span>
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;
@ViewChild("tpl") tpl: TemplateRef<any>;
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
this.vc.insert(view);
}
}
Note: to get ViewContaierRef
through @ViewChild
, must provide {read: ViewContainerRef}
argument, because <ng-container>
element itself is not ViewContainer
.
To remove view from ViewContainer
, call .detach()
method.
ViewContainer
defines convenient methods to create views:
class ViewContainerRef {
createComponent(componentFactory...): ComponentRef<C>;
createEmbeddedView(templateRef...): EmbeddedViewRef<C>;
}