具有 user-click 个选定组件的动态选项卡
Dynamic tabs with user-click chosen components
我正在尝试设置一个允许组件自行注册(带有标题)的选项卡系统。第一个选项卡就像一个收件箱,有很多 actions/link 项目可供用户选择,每次点击都应该能够在点击时实例化一个新组件。操作/链接来自 JSON.
实例化的组件随后会将自己注册为新选项卡。
我不确定这是否是 'best' 方法?到目前为止,我看到的唯一指南是针对静态选项卡的,这没有帮助。
到目前为止,我只有在 main 中引导的选项卡服务在整个应用程序中持续存在。它看起来像这样:
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 并显示多个项目。单击其中一个项目后,将使用该项目操作创建一个新选项卡 'type'。该类型就是一个组件。
编辑 2: Image.
更新
更新
ngComponentOutlet
已添加到 4.0.0-beta.3
更新
有一个 NgComponentOutlet
正在进行的工作做类似的事情 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();
}
}
}
使用示例
// 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
旧版本 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这在 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.4
Plunker 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();
}
}
}
原创
从你的问题中不能完全确定你的要求是什么,但我认为这应该符合你的要求。
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
我不够酷,无法发表评论。我从接受的答案中修复了 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'
},
有组件可供使用(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);
});
}
}
我正在尝试设置一个允许组件自行注册(带有标题)的选项卡系统。第一个选项卡就像一个收件箱,有很多 actions/link 项目可供用户选择,每次点击都应该能够在点击时实例化一个新组件。操作/链接来自 JSON.
实例化的组件随后会将自己注册为新选项卡。
我不确定这是否是 'best' 方法?到目前为止,我看到的唯一指南是针对静态选项卡的,这没有帮助。
到目前为止,我只有在 main 中引导的选项卡服务在整个应用程序中持续存在。它看起来像这样:
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 并显示多个项目。单击其中一个项目后,将使用该项目操作创建一个新选项卡 'type'。该类型就是一个组件。
编辑 2: Image.
更新
更新
ngComponentOutlet
已添加到 4.0.0-beta.3
更新
有一个 NgComponentOutlet
正在进行的工作做类似的事情 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();
}
}
}
使用示例
// 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
旧版本 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这在 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.4
Plunker 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();
}
}
}
原创
从你的问题中不能完全确定你的要求是什么,但我认为这应该符合你的要求。
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
我不够酷,无法发表评论。我从接受的答案中修复了 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'
},
有组件可供使用(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);
});
}
}