首页 文章

带有用户单击所选组件的Angular动态选项卡

提问于
浏览
179

我正在尝试设置一个标签系统,允许组件自己注册(带 Headers ) . 第一个选项卡就像一个收件箱,有很多动作/链接项可供用户选择,每个点击都应该能够在点击时实例化一个新组件 . 动作/链接来自JSON .

然后,实例化的组件将自己注册为新选项卡 .

我不确定这是否是“最佳”方法? Sofar我见过的唯一指南是静态标签,这没有帮助 .

到目前为止,我只有在主要引导程序中的标签服务在整个应用程序中持续存在,看起来像这样的东西 .

export interface ITab { title: string; }

@Injectable()
export class TabsService {
    private tabs = new Set<ITab>();

    addTab(title: string): ITab {
        let tab: ITab = { title };
        this.tabs.add(tab);
        return tab;
    }

    removeTab(tab: ITab) {
        this.tabs.delete(tab);
    }
}

问题:

  • 如何在收件箱中创建一个动态列表来创建新的(不同的)标签?我猜是否会使用 DynamicComponentBuilder

  • 如何从收件箱创建的组件(单击时)将自己注册为选项卡并显示?我猜 ng-content ,但我找不到有关如何使用它的更多信息

编辑:尝试澄清

将收件箱视为邮件收件箱,将项目作为JSON提取并显示多个项目 . 单击其中一个项目后,将创建一个包含该项操作“类型”的新选项卡 . 那么类型就是一个组件

Edit2:图片

http://i.imgur.com/yzfMOXJ.png

3 回答

  • 233

    update

    Angular 5 StackBlitz example

    update

    ngComponentOutlet 已添加到4.0.0-beta.3

    update

    有一个正在进行的 NgComponentOutlet 工作做了类似的事情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();
        }    
      }
    }
    

    用法示例

    // 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 {}
    

    另见angular.io DYNAMIC COMPONENT LOADER

    older versions xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    这在Angular2 RC.5中再次发生了变化

    我将更新下面的示例,但这是休假前的最后一天 .

    这个Plunker example演示了如何在RC.5中动态创建组件

    Update - use ViewContainerRef.createComponent()

    由于不推荐使用 DynamicComponentLoader ,因此需要再次更新该方法 .

    @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

    从你的问题不完全确定你的要求是什么,但我认为这应该做你想要的 .

    Tabs 组件获取传递的类型数组,并为数组中的每个项创建"tabs" .

    @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(不是基于你的Plunker)

    还有一种传递数据的方法可以传递给动态创建的组件,如( someData 需要像 type 一样传递)

    this.dcl.loadIntoLocation(this.type, this.elRef, 'target').then((cmpRef) => {
      cmpRef.instance.someProperty = someData;
      this.cmpRef = cmpRef;
    });
    

    还有一些支持将依赖注入与共享服务一起使用 .

    有关详细信息,请参阅https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html

  • 17

    我的评论不够酷 . 我从接受的答案修复了关于rc2工作的plunker . 没有什么花哨的,与CDN的链接只是被打破了 .

    '@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

  • 14

    有组件可以使用(rc5兼容)ng2-steps使用 Compiler 将组件注入步骤容器和服务,以便将所有内容连接在一起(数据同步)

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

相关问题