Dynamic tabs with user-click chosen components
update
update
ngComponentOutlet
was added to 4.0.0-beta.3
update
There is a NgComponentOutlet
work in progress that does something similar https://github.com/angular/angular/pull/11235
RC.7
// Helper component to add dynamic components@Component({ selector: 'dcl-wrapper', template: `<div #target></div>`})export class DclWrapper { @ViewChild('target', {read: ViewContainerRef}) target: ViewContainerRef; @Input() type: Type<Component>; cmpRef: ComponentRef<Component>; private isViewInitialized:boolean = false; constructor(private componentFactoryResolver: ComponentFactoryResolver, private compiler: Compiler) {} updateComponent() { if(!this.isViewInitialized) { return; } if(this.cmpRef) { // when the `type` input changes we destroy a previously // created component before creating the new one this.cmpRef.destroy(); } let factory = this.componentFactoryResolver.resolveComponentFactory(this.type); this.cmpRef = this.target.createComponent(factory) // to access the created instance use // this.compRef.instance.someProperty = 'someValue'; // this.compRef.instance.someOutput.subscribe(val => doSomething()); } ngOnChanges() { this.updateComponent(); } ngAfterViewInit() { this.isViewInitialized = true; this.updateComponent(); } ngOnDestroy() { if(this.cmpRef) { this.cmpRef.destroy(); } }}
Usage example
// Use dcl-wrapper component@Component({ selector: 'my-tabs', template: ` <h2>Tabs</h2> <div *ngFor="let tab of tabs"> <dcl-wrapper [type]="tab"></dcl-wrapper> </div>`})export class Tabs { @Input() tabs;}
@Component({ selector: 'my-app', template: ` <h2>Hello {{name}}</h2> <my-tabs [tabs]="types"></my-tabs>`})export class App { // The list of components to create tabs from types = [C3, C1, C2, C3, C3, C1, C1];}
@NgModule({ imports: [ BrowserModule ], declarations: [ App, DclWrapper, Tabs, C1, C2, C3], entryComponents: [C1, C2, C3], bootstrap: [ App ]})export class AppModule {}
See also angular.io DYNAMIC COMPONENT LOADER
older versions xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
This changed again in Angular2 RC.5
I will update the example below but it's the last day before vacation.
This Plunker example demonstrates how to dynamically create components in RC.5
Update - use ViewContainerRef.createComponent()
Because DynamicComponentLoader
is deprecated, the approach needs to be update again.
@Component({ selector: 'dcl-wrapper', template: `<div #target></div>`})export class DclWrapper { @ViewChild('target', {read: ViewContainerRef}) target; @Input() type; cmpRef:ComponentRef; private isViewInitialized:boolean = false; constructor(private resolver: ComponentResolver) {} updateComponent() { if(!this.isViewInitialized) { return; } if(this.cmpRef) { this.cmpRef.destroy(); } this.resolver.resolveComponent(this.type).then((factory:ComponentFactory<any>) => { this.cmpRef = this.target.createComponent(factory) // to access the created instance use // this.compRef.instance.someProperty = 'someValue'; // this.compRef.instance.someOutput.subscribe(val => doSomething()); }); } ngOnChanges() { this.updateComponent(); } ngAfterViewInit() { this.isViewInitialized = true; this.updateComponent(); } ngOnDestroy() { if(this.cmpRef) { this.cmpRef.destroy(); } }}
Plunker example RC.4
Plunker example beta.17
Update - use loadNextToLocation
export class DclWrapper { @ViewChild('target', {read: ViewContainerRef}) target; @Input() type; cmpRef:ComponentRef; private isViewInitialized:boolean = false; constructor(private dcl:DynamicComponentLoader) {} updateComponent() { // should be executed every time `type` changes but not before `ngAfterViewInit()` was called // to have `target` initialized if(!this.isViewInitialized) { return; } if(this.cmpRef) { this.cmpRef.destroy(); } this.dcl.loadNextToLocation(this.type, this.target).then((cmpRef) => { this.cmpRef = cmpRef; }); } ngOnChanges() { this.updateComponent(); } ngAfterViewInit() { this.isViewInitialized = true; this.updateComponent(); } ngOnDestroy() { if(this.cmpRef) { this.cmpRef.destroy(); } }}
original
Not entirely sure from your question what your requirements are but I think this should do what you want.
The Tabs
component gets an array of types passed and it creates "tabs" for each item in the array.
@Component({ selector: 'dcl-wrapper', template: `<div #target></div>`})export class DclWrapper { constructor(private elRef:ElementRef, private dcl:DynamicComponentLoader) {} @Input() type; ngOnChanges() { if(this.cmpRef) { this.cmpRef.dispose(); } this.dcl.loadIntoLocation(this.type, this.elRef, 'target').then((cmpRef) => { this.cmpRef = cmpRef; }); }}@Component({ selector: 'c1', template: `<h2>c1</h2>`})export class C1 {}@Component({ selector: 'c2', template: `<h2>c2</h2>`})export class C2 {}@Component({ selector: 'c3', template: `<h2>c3</h2>`})export class C3 {}@Component({ selector: 'my-tabs', directives: [DclWrapper], template: ` <h2>Tabs</h2> <div *ngFor="let tab of tabs"> <dcl-wrapper [type]="tab"></dcl-wrapper> </div>`})export class Tabs { @Input() tabs;}@Component({ selector: 'my-app', directives: [Tabs] template: ` <h2>Hello {{name}}</h2> <my-tabs [tabs]="types"></my-tabs>`})export class App { types = [C3, C1, C2, C3, C3, C1, C1];}
Plunker example beta.15 (not based on your Plunker)
There is also a way to pass data along that can be passed to the dynamically created component like (someData
would need to be passed like type
)
this.dcl.loadIntoLocation(this.type, this.elRef, 'target').then((cmpRef) => { cmpRef.instance.someProperty = someData; this.cmpRef = cmpRef;});
There is also some support to use dependency injection with shared services.
For more details see https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html
I'm not cool enough for comments. I fixed the plunker from the accepted answer to work for rc2. Nothing fancy, links to the CDN were just broken is all.
'@angular/core': { main: 'bundles/core.umd.js', defaultExtension: 'js'},'@angular/compiler': { main: 'bundles/compiler.umd.js', defaultExtension: 'js'},'@angular/common': { main: 'bundles/common.umd.js', defaultExtension: 'js'},'@angular/platform-browser-dynamic': { main: 'bundles/platform-browser-dynamic.umd.js', defaultExtension: 'js'},'@angular/platform-browser': { main: 'bundles/platform-browser.umd.js', defaultExtension: 'js'},
there is component ready to use (rc5 compatible)ng2-stepswhich uses Compiler
to inject component to step containerand service for wiring everything together (data sync)
import { Directive , Input, OnInit, Compiler , ViewContainerRef } from '@angular/core';import { StepsService } from './ng2-steps';@Directive({ selector:'[ng2-step]'})export class StepDirective implements OnInit{ @Input('content') content:any; @Input('index') index:string; public instance; constructor( private compiler:Compiler, private viewContainerRef:ViewContainerRef, private sds:StepsService ){} ngOnInit(){ //Magic! this.compiler.compileComponentAsync(this.content).then((cmpFactory)=>{ const injector = this.viewContainerRef.injector; this.viewContainerRef.createComponent(cmpFactory, 0, injector); }); }}