如何呈现实现接口的组件?
How can I render a component that implements an interface?
假设我有一个接口(或实际组件)ListItemRenderer
和一个实现该接口(或扩展基础组件)的组件MyRendererComponent
@Component({
selector: 'my-renderer',
template: '<div>My renderer</div>'
})
export class MyRendererComponent implements ListItemRenderer {
...
}
我想将这个具体实现传递给另一个组件,例如
@Component({
selector: 'my-list',
template: `
<ul [renderer]="renderer" [items]="items">
<li *ngFor="let item of items">
<!-- what goes here? -->
</li>
</ul>
`
})
export class MyList {
@Input() renderer: ListItemRenderer;
@Input() items: any[];
...
}
显然,父组件会有 public 属性 renderer
类型 ListItemRenderer
。问题是,如何在我的 <li>
中使用该组件(参见上面的“这里有什么?”)?
要使用 *ngFor
动态添加组件,您需要像 中解释的 dcl-wrapper
之类的东西(DynamicComponentLoader
已被弃用,取而代之的是 ViewContainerRef.createComponent()
但我没有尝试为包装器组件引入另一个名称。)
@Component({
selector: '[dcl-wrapper]', // changed selector in order to be used with `<li>`
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)
});
}
ngOnChanges() {
this.updateComponent();
}
ngAfterViewInit() {
this.isViewInitialized = true;
this.updateComponent();
}
ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}
并像
一样使用它
@Component({
selector: 'my-list',
template: `
<ul [items]="items">
<li *ngFor="let item of items" dcl-wrapper [type]="renderer" ></li>
</ul>
`
})
export class MyList {
@Input() renderer: ListItemRenderer;
@Input() items: any[];
...
}
Günter Zöchbauer 的回答很准确,这里有一个更完整的代码示例,以防其他人遇到此问题 (Angular2 RC1)。
app.component.ts
import { Component } from '@angular/core';
import { DynamicListComponent } from './dynamic-list.component';
import { TwoRendererComponent } from './two-renderer.component';
import { Renderer } from './renderer';
@Component({
selector: 'app',
template: `
<h2>Dynamic List</h2>
<dynamic-list [items]="items" [renderer]="renderer"></dynamic-list>
`,
directives: [
DynamicListComponent
]
})
export class AppComponent {
items: string[] = [ 'one', 'two', 'three' ];
renderer: Renderer;
constructor() {
this.renderer = TwoRendererComponent;
}
}
renderer.ts
export class Renderer {
}
动态-list.component.ts
import { Component, Input } from '@angular/core';
import { Renderer } from './renderer';
import { DclWrapperComponent } from './dcl-wrapper.component';
@Component({
selector: 'dynamic-list',
template: `
<ul>
<li *ngFor="let item of items" dcl-wrapper [type]="renderer">
</li>
</ul>
`,
directives: [
DclWrapperComponent
]
})
export class DynamicListComponent {
@Input() items: string[] = [];
@Input() renderer: any;
}
一个-renderer.component.ts
import { Component } from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: 'one-renderer',
template: '<div>ONE</div>'
})
export class OneRendererComponent implements Renderer {
}
二-renderer.component.ts
import { Component } from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: 'two-renderer',
template: '<div>TWO</div>'
})
export class TwoRendererComponent implements Renderer {
}
dcl-wrapper.component.ts
import {
Component,
ViewChild,
ViewContainerRef,
ComponentRef,
ComponentResolver,
ComponentFactory,
Input
} from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: '[dcl-wrapper]',
template: `<div #target></div>`
})
export class DclWrapperComponent {
@ViewChild('target', { read: ViewContainerRef }) target;
@Input() type: any;
@Input() input: string;
cmpRef: ComponentRef<Renderer>;
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)
});
}
ngOnChanges() {
this.updateComponent();
}
ngAfterViewInit() {
this.isViewInitialized = true;
this.updateComponent();
}
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
结果 DOM 如下所示:
<app>
<h2>Dynamic List</h2>
<dynamic-list>
<ul>
<!--template bindings={}-->
<li dcl-wrapper="">
<div></div>
<two-renderer _ngcontent-gce-4="">
<div>TWO</div>
</two-renderer>
</li>
...
</ul>
</dynamic-list>
</app>
如果您需要传递参数,则必须像这样将其传递给 DclWrapperComponent
:
dcl-wrapper.component.ts
export class DclWrapperComponent {
@ViewChild('target', { read: ViewContainerRef }) target;
@Input() type: any;
@Input() input: string; // <-- the parameter
cmpRef: ComponentRef<Renderer>;
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);
this.cmpRef.instance.input = this.input; // <-- pass value to the newly created component
});
}
...
这是一个示例实现:
转大写-renderer.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: 'to-uppercase-renderer',
template: '<div>{{output}}</div>'
})
export class ToUppercaseRendererComponent implements Renderer, OnInit {
@Input() input: string;
output: string;
ngOnInit() {
this.output = this.input.toUpperCase();
}
}
当然,参数也应该在基础组件上声明,
renderer.ts
import { Input } from '@angular/core';
export abstract class Renderer {
@Input() input: string;
}
然后您可以按如下方式在模板中传递此参数:
动态-list.component.ts
...
@Component({
selector: 'dynamic-list',
template: `
<ul>
<li *ngFor="let item of items" dcl-wrapper [type]="renderer" [input]="item">
</li>
</ul>
`,
directives: [
DclWrapperComponent
]
})
...
假设我有一个接口(或实际组件)ListItemRenderer
和一个实现该接口(或扩展基础组件)的组件MyRendererComponent
@Component({
selector: 'my-renderer',
template: '<div>My renderer</div>'
})
export class MyRendererComponent implements ListItemRenderer {
...
}
我想将这个具体实现传递给另一个组件,例如
@Component({
selector: 'my-list',
template: `
<ul [renderer]="renderer" [items]="items">
<li *ngFor="let item of items">
<!-- what goes here? -->
</li>
</ul>
`
})
export class MyList {
@Input() renderer: ListItemRenderer;
@Input() items: any[];
...
}
显然,父组件会有 public 属性 renderer
类型 ListItemRenderer
。问题是,如何在我的 <li>
中使用该组件(参见上面的“这里有什么?”)?
要使用 *ngFor
动态添加组件,您需要像 dcl-wrapper
之类的东西(DynamicComponentLoader
已被弃用,取而代之的是 ViewContainerRef.createComponent()
但我没有尝试为包装器组件引入另一个名称。)
@Component({
selector: '[dcl-wrapper]', // changed selector in order to be used with `<li>`
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)
});
}
ngOnChanges() {
this.updateComponent();
}
ngAfterViewInit() {
this.isViewInitialized = true;
this.updateComponent();
}
ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}
并像
一样使用它@Component({
selector: 'my-list',
template: `
<ul [items]="items">
<li *ngFor="let item of items" dcl-wrapper [type]="renderer" ></li>
</ul>
`
})
export class MyList {
@Input() renderer: ListItemRenderer;
@Input() items: any[];
...
}
Günter Zöchbauer 的回答很准确,这里有一个更完整的代码示例,以防其他人遇到此问题 (Angular2 RC1)。
app.component.ts
import { Component } from '@angular/core';
import { DynamicListComponent } from './dynamic-list.component';
import { TwoRendererComponent } from './two-renderer.component';
import { Renderer } from './renderer';
@Component({
selector: 'app',
template: `
<h2>Dynamic List</h2>
<dynamic-list [items]="items" [renderer]="renderer"></dynamic-list>
`,
directives: [
DynamicListComponent
]
})
export class AppComponent {
items: string[] = [ 'one', 'two', 'three' ];
renderer: Renderer;
constructor() {
this.renderer = TwoRendererComponent;
}
}
renderer.ts
export class Renderer {
}
动态-list.component.ts
import { Component, Input } from '@angular/core';
import { Renderer } from './renderer';
import { DclWrapperComponent } from './dcl-wrapper.component';
@Component({
selector: 'dynamic-list',
template: `
<ul>
<li *ngFor="let item of items" dcl-wrapper [type]="renderer">
</li>
</ul>
`,
directives: [
DclWrapperComponent
]
})
export class DynamicListComponent {
@Input() items: string[] = [];
@Input() renderer: any;
}
一个-renderer.component.ts
import { Component } from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: 'one-renderer',
template: '<div>ONE</div>'
})
export class OneRendererComponent implements Renderer {
}
二-renderer.component.ts
import { Component } from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: 'two-renderer',
template: '<div>TWO</div>'
})
export class TwoRendererComponent implements Renderer {
}
dcl-wrapper.component.ts
import {
Component,
ViewChild,
ViewContainerRef,
ComponentRef,
ComponentResolver,
ComponentFactory,
Input
} from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: '[dcl-wrapper]',
template: `<div #target></div>`
})
export class DclWrapperComponent {
@ViewChild('target', { read: ViewContainerRef }) target;
@Input() type: any;
@Input() input: string;
cmpRef: ComponentRef<Renderer>;
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)
});
}
ngOnChanges() {
this.updateComponent();
}
ngAfterViewInit() {
this.isViewInitialized = true;
this.updateComponent();
}
ngOnDestroy() {
if (this.cmpRef) {
this.cmpRef.destroy();
}
}
}
结果 DOM 如下所示:
<app>
<h2>Dynamic List</h2>
<dynamic-list>
<ul>
<!--template bindings={}-->
<li dcl-wrapper="">
<div></div>
<two-renderer _ngcontent-gce-4="">
<div>TWO</div>
</two-renderer>
</li>
...
</ul>
</dynamic-list>
</app>
如果您需要传递参数,则必须像这样将其传递给 DclWrapperComponent
:
dcl-wrapper.component.ts
export class DclWrapperComponent {
@ViewChild('target', { read: ViewContainerRef }) target;
@Input() type: any;
@Input() input: string; // <-- the parameter
cmpRef: ComponentRef<Renderer>;
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);
this.cmpRef.instance.input = this.input; // <-- pass value to the newly created component
});
}
...
这是一个示例实现:
转大写-renderer.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Renderer } from './renderer';
@Component({
selector: 'to-uppercase-renderer',
template: '<div>{{output}}</div>'
})
export class ToUppercaseRendererComponent implements Renderer, OnInit {
@Input() input: string;
output: string;
ngOnInit() {
this.output = this.input.toUpperCase();
}
}
当然,参数也应该在基础组件上声明,
renderer.ts
import { Input } from '@angular/core';
export abstract class Renderer {
@Input() input: string;
}
然后您可以按如下方式在模板中传递此参数:
动态-list.component.ts
...
@Component({
selector: 'dynamic-list',
template: `
<ul>
<li *ngFor="let item of items" dcl-wrapper [type]="renderer" [input]="item">
</li>
</ul>
`,
directives: [
DclWrapperComponent
]
})
...