我正在尝试设置一个标签系统,允许组件注册自己(带有标题)。第一个选项卡就像一个收件箱,有很多动作/链接项可供用户选择,每次点击都应该能够在点击时实例化一个新组件。动作/链接来自 JSON。
然后实例化的组件将自己注册为一个新选项卡。
我不确定这是否是“最佳”方法?到目前为止,我看到的唯一指南是针对静态选项卡的,这无济于事。
到目前为止,我只有在 main 中引导以在整个应用程序中持续存在的 tabs 服务。它看起来像这样:
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 格式获取,并显示多个项目。单击其中一个项目后,将使用该项目操作“类型”创建一个新选项卡。那么类型就是一个组件。
编辑 2: 图像。
更新
Angular 5 StackBlitz 示例
ngComponentOutlet被添加到 4.0.0-beta.3
ngComponentOutlet
NgComponentOutlet正在进行一项类似https://github.com/angular/angular/pull/11235 的工作
NgComponentOutlet
RC.7
Plunker 示例 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 动态组件加载器
旧版本 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这在 Angular2 RC.5 中再次发生了变化
我将更新下面的示例,但这是假期前的最后一天。
这个Plunker 示例演示了如何在 RC.5 中动态创建组件
更新 - 使用 ViewContainerRef .createComponent()
由于DynamicComponentLoader已弃用,该方法需要再次更新。
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 示例 RC.4 Plunker 示例 beta.17
更新 - 使用 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 示例 beta.17
原来的
从您的问题中不能完全确定您的要求是什么,但我认为这应该符合您的要求。
该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 示例 beta.15 (不是基于您的 Plunker)
还有一种方法可以传递数据,可以传递给动态创建的组件,例如(someData需要像传递一样type)
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