Dynamic tabs with user-click chosen components Dynamic tabs with user-click chosen components angular angular

Dynamic tabs with user-click chosen components


update

Angular 5 StackBlitz example

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

Plunker example 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();    }      }}

Plunker example beta.17

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'},

https://plnkr.co/edit/kVJvI1vkzrLZJeRFsZuv?p=preview


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);    });  }}