带有用户单击所选组件的动态选项卡
Posted
技术标签:
【中文标题】带有用户单击所选组件的动态选项卡【英文标题】:Dynamic tabs with user-click chosen components 【发布时间】:2016-12-28 23:35:29 【问题描述】:我正在尝试设置一个标签系统,允许组件注册自己(带有标题)。第一个选项卡就像一个收件箱,有很多动作/链接项可供用户选择,每次点击都应该能够在点击时实例化一个新组件。动作/链接来自 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: Image.
【问题讨论】:
如果选项卡中显示的组件在构建时未知,那么 DCL 是正确的方法。 我不太清楚你的要求,所以很难在没有工作代码/plunker的情况下告诉你任何事情。看看这个,如果它可以帮助你plnkr.co/edit/Ud1x10xee7BmtUaSAA2R?p=preview(我不知道它是否相关) @micronyks 我认为你的链接有误 嗨!我正在尝试做你所要求的。到目前为止,我设法创建了具有动态内容的选项卡,但是我没有找到一种令人满意的方法来在选项卡更改时保持组件状态(加载的组件可能非常不同)。你是怎么做到的? 【参考方案1】:更新
Angular 5 StackBlitz example
更新
ngComponentOutlet
被添加到 4.0.0-beta.3
更新
有一个 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
旧版本 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这在 Angular2 RC.5 中再次改变
我将更新下面的示例,但这是假期前的最后一天。
这个Plunker example 演示了如何在 RC.5 中动态创建组件
更新 - 使用 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.4Plunker example 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 example beta.17
原创
从您的问题中不能完全确定您的要求是什么,但我认为这应该可以满足您的要求。
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
【讨论】:
当然,您只需要将组件类型设置为DclWrapper
即可创建实际实例。
@Joseph 可以注入ViewContainerRef
而不是使用ViewChild
,然后<dcl-wrapper>
本身成为目标。元素作为目标的兄弟元素添加,因此会在 <dcl-wrapper>
之外。
不支持替换。可以把模板改成''
(空字符串)`,把构造函数改成constructor(private target:ViewContainerRef)
,那么动态添加的组件就变成<dcl-wrapper>
的兄弟了
我使用的是 RC4,这个例子非常有用。我只想提到的是我必须添加下面的代码才能正常工作 this.cmpRef.changeDetectorRef.detectChanges();
在使用 ngAfterViewInit 时动态组件有另一个 dynaimc 组件时出现错误。改为 ngAfterContentInit,现在它正在使用嵌套的动态组件【参考方案2】:
我对 cmets 还不够酷。我从接受的答案中修复了 plunker,以便为 rc2 工作。没什么特别的,只是 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
【讨论】:
【参考方案3】:有可以使用的组件(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);
);
【讨论】:
以上是关于带有用户单击所选组件的动态选项卡的主要内容,如果未能解决你的问题,请参考以下文章