方案一:Directive
item.directive.ts
import {
Input,
Directive,
TemplateRef,
EmbeddedViewRef,
ViewContainerRef,
} from '@angular/core';
@Directive({
selector: '[ListItem]',
})
export class ListItem {
private _context: any;
private _viewRef: EmbeddedViewRef<any>;
constructor(
private viewContainer: ViewContainerRef,
public templateRef: TemplateRef<any>
) {
this._viewRef = this.viewContainer.createEmbeddedView(templateRef);
}
@Input()
set ItemDirective(source: any) {
this._context = source;
}
ngAfterViewInit(): void {
// get the real dom:
// this._viewRef.rootNodes.find((item) => item.nodeType !== 8);
}
}
list.component.ts
@Component({
selector: 'list, [list]',
template: `
<ng-container *ngFor="let item of items">
<ng-template [ListItem]="item">
<ng-container
*ngTemplateOutlet="
listItemTemplateRef;
context: { $implicit: item }
"
></ng-container>
</ng-template>
</ng-container>
`,
})
export class ListComponent implements OnInit {
@Input() items = [];
}
如何使用:
@Component({
selector: 'tester',
template: `
<list [items]="items">
<ng-template let-data>
<p>{{ data.id }}</p>
</ng-template>
</list>
`,
styles: [],
})
export class TesterComponent {
public items = [
{ id: 'a', text: 'aaa' },
{ id: 'b', text: 'bbb' },
{ id: 'c', text: 'ccc' },
...
];
}
缺点:
使用这种方式可能会渲染较多的注释片段,由于angular在开发模式下会添加注释来帮助开发者理解数据绑定的状态,但在生产模式中通常会被移除。
打开Element可能会看见以下信息:
<!--bindings={
"ng-reflect-ng-template-outlet-context": "[object Object]"
}-->
<!--bindings={
"ng-reflect-is-horizontal": "false"
}-->
<!--ng-container-->
方案二:Directive + viewContainerRef
item.directive.ts
import { Directive, TemplateRef } from '@angular/core';
@Directive({
selector: '[ListItem]',
})
export class VirtualItem {
constructor(public templateRef: TemplateRef<any>) {}
}
list.component.ts
import { ViewContainerRef } from '@angular/core';
import { ListItem } from "./item.directive";
@Component({
selector: 'list, [list]',
template: `
<ng-container #viewContainer></ng-container>
`,
})
export class ListComponent implements OnInit {
@Input() items = [];
@ViewChild("hostView", { read: ViewContainerRef, static: true })
public viewContainerRef!: ViewContainerRef;
@ContentChild(ListItem)
@DeclareState()
public ListItem!;
public renders = [];
// 渲染列表内容
renderItems() {
// 如果内容有变化,先清除已渲染内容
this.viewContainerRef.clear();
this.renders = [];
this.items.forEach((item, index) => {
this.renders.push(
this.viewContainerRef.createEmbeddedView(
this.ListItem.templateRef,
{ $implicit: item, index }
)
);
});
}
}
如何使用:
@Component({
selector: 'tester',
template: `
<list [items]="items">
<div *ListItem="let item">
<p>{{ item.text }}</p>
</div>
</list>
`,
styles: [],
})
export class TesterComponent {
public items = [
{ id: 'a', text: 'aaa' },
{ id: 'b', text: 'bbb' },
{ id: 'c', text: 'ccc' },
...
];
}