我有一个ButtonGroup组件,它将渲染一定数量的ButtonAction组件。 我试着给每个ButtonAction分配一个模板属性(TemplateRef),这样我就可以循环并将它们传递给一个ng-template(通过*ngTemplateOutlet)。 我直接将TemplateRef注入到ButtonAction的构造函数中,但是我得到了错误的信息。"没有TemplateRef的提供者". 由于我的目的是只呈现一个 数量有限 的子代,我找到的另一个解决方案是通过指令来访问模板,但我不想强迫我们的用户在每个子代上使用指令。但我不想强迫我们的用户在每个子代上使用指令。 那么,我该怎么做呢?
@Component({
selector: 'button-group',
template: `
<div>
<ng-content *ngIf="canDisplayAllChildren; else limitedChildren"></ng-content>
<ng-template #limitedChildren>
<ng-container *ngFor="let button of buttons">
<ng-template [ngTemplateOutlet]="button.template"></ng-template>
</ng-container>
</ng-template>
<button-action (click)="toggle()" *ngIf="shouldLimitChildren">
<icon [name]="'action-more-fill-vert'"></icon>
</button-action>
</div>
`,
})
export class ButtonGroupComponent {
@Input()
public maxVisible: number;
@ContentChildren(ButtonActionComponent)
public buttons: QueryList<ButtonActionComponent>;
public isClosed: boolean = true;
public toggle() {
this.isClosed = !this.isClosed;
}
public get shouldLimitChildren() {
return this.hasLimit && this.buttons.length > this.maxVisible;
}
public get canDisplayAllChildren() {
return !this.shouldLimitChildren || this.isOpen;
}
}
ButtonActionComponent在哪里。
@Component({
selector: "button-action",
template: `
...
`
})
export class ButtonActionComponent {
...
constructor(public element: ElementRef, public template: TemplateRef<any>) {}
}
我花了一些时间来想出一个假想的解决方案,但我想我可能有一些有用的东西,不依赖于显式指令添加到你的组件子代。
无法使用 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.coma343277543359473。):
// 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
所以我们需要在运行时将其添加到 entryComponents
阵的 AppModule
(这里是参考资料。https:/angular.ioguideentry-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>
在这种情况下,只有第一,第二和第三子代应该被渲染。
我测试一切的codesandbox就是这个。https:/codesandbox.iosnervous-darkness-6zorf?file=srcappapp.component.html。
希望对你有所帮助。