我花了一些时间想出一个假想的解决方案,但我认为我可能有一些有用的东西,它不依赖于添加到您的子组件的显式指令。
由于无法在不涉及结构指令的情况下使用TemplateRef,我想到了一种类似于React.cloneElement API 的机制。
所以,让我们定义一个基本的ButtonComponent,它将用作ButtonGroupComponent 的子代。
// button.component.ts
import { Component, Input } from "@angular/core";
@Component({
selector: "app-button",
template: `
<button>{{ text }}</button>
`
})
export class ButtonComponent {
@Input()
public text: string;
}
GroupComponent 应该克隆并仅将通过maxVisible 输入属性指定的子节点数量附加到其视图中,我还为根本没有提供它的情况提供了POSITIVE_INFINITY 默认值,允许显示所有孩子:
// group.component.ts
...
@Input()
public maxVisible: number = Number.POSITIVE_INFINITY;
...
让我们要求 Angular 在我们的内容中提供孩子(我想说这是对差异的最好解释:https://stackoverflow.com/a/34327754/3359473):
// group.component.ts
...
@ContentChildren(ButtonComponent)
private children: QueryList<ButtonComponent>;
...
我们现在需要让 Angular 注入一些东西:
- 我们当前手动将子实例化到的容器;
- 一个工厂解析器,可帮助我们动态创建组件;
// group.component.ts
...
constructor(
private container: ViewContainerRef,
private factoryResolver: ComponentFactoryResolver
) {}
private factory = this.factoryResolver.resolveComponentFactory(ButtonComponent);
...
现在我们已经从 Angular 获得了我们需要的任何东西,我们可以拦截实现 AfterContentInit 接口并添加 ngAfterContentInit 生命周期的内容初始化。
我们需要循环遍历我们的子组件,动态创建新组件并将新组件的所有公共属性设置为给定的子组件:
// group.component.ts
...
ngAfterContentInit() {
Promise.resolve().then(this.initChildren);
}
private initChildren = () => {
// here we are converting the QueryList to an array
this.children.toArray()
// here we are taking only the elements we need to show
.slice(0, this.maxVisible)
// and for each child
.forEach(child => {
// we create the new component in the container injected
// in the constructor the using the factory we created from
// the resolver, also given by Angular in our constructor
const component = this.container.createComponent(this.factory);
// we clone all the properties from the user-given child
// to the brand new component instance
this.clonePropertiesFrom(child, component.instance);
});
};
// nothing too fancy here, just cycling all the properties from
// one object and setting with the same values on another object
private clonePropertiesFrom(from: ButtonComponent, to: ButtonComponent) {
Object.getOwnPropertyNames(from).forEach(property => {
to[property] = from[property];
});
}
...
完整的GroupComponent 应如下所示:
// group.component.ts
import {
Component,
ContentChildren,
QueryList,
AfterContentInit,
ViewContainerRef,
ComponentFactoryResolver,
Input
} from "@angular/core";
import { ButtonComponent } from "./button.component";
@Component({
selector: "app-group",
template: ``
})
export class GroupComponent implements AfterContentInit {
@Input()
public maxVisible: number = Number.POSITIVE_INFINITY;
@ContentChildren(ButtonComponent)
public children: QueryList<ButtonComponent>;
constructor(
private container: ViewContainerRef,
private factoryResolver: ComponentFactoryResolver
) {}
private factory = this.factoryResolver.resolveComponentFactory(
ButtonComponent
);
ngAfterContentInit() {
Promise.resolve().then(this.initChildren);
}
private initChildren = () => {
this.children
.toArray()
.slice(0, this.maxVisible)
.forEach(child => {
const component = this.container.createComponent(this.factory);
this.clonePropertiesFrom(child, component.instance);
});
};
private clonePropertiesFrom(from: ButtonComponent, to: ButtonComponent) {
Object.getOwnPropertyNames(from).forEach(property => {
to[property] = from[property];
});
}
}
注意我们是在运行时创建ButtonComponent,所以我们需要将它添加到AppModule的entryComponents数组中(这里是参考:https://angular.io/guide/entry-components)。
// app.module.ts
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { AppComponent } from "./app.component";
import { ButtonComponent } from "./button.component";
import { GroupComponent } from "./group.component";
@NgModule({
declarations: [AppComponent, ButtonComponent, GroupComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent],
entryComponents: [ButtonComponent]
})
export class AppModule {}
使用这两个简单的组件,您应该能够仅渲染给定子组件的子集,并保持非常清晰的用法:
<!-- app.component.html -->
<app-group [maxVisible]="3">
<app-button [text]="'Button 1'"></app-button>
<app-button [text]="'Button 2'"></app-button>
<app-button [text]="'Button 3'"></app-button>
<app-button [text]="'Button 4'"></app-button>
<app-button [text]="'Button 5'"></app-button>
</app-group>
在这种情况下,应该只渲染第一个、第二个和第三个孩子。
我测试过的所有代码框都是这个:
https://codesandbox.io/s/nervous-darkness-6zorf?file=/src/app/app.component.html
希望这会有所帮助。