Option One (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 = [];
}
Usage
@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' },
...
];
}
Disadvantages
Using this method may render a large number of comment fragments. Because Angular adds comments in development mode to help developers understand the status of data binding, but these comments are usually removed in production mode.
You may see the following information when opening Element:
<!--bindings={
"ng-reflect-ng-template-outlet-context": "[object Object]"
}-->
<!--bindings={
"ng-reflect-is-horizontal": "false"
}-->
<!--ng-container-->
Option Two (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() {
// clear the rendered content first
this.viewContainerRef.clear();
this.renders = [];
this.items.forEach((item, index) => {
this.renders.push(
this.viewContainerRef.createEmbeddedView(
this.ListItem.templateRef,
{ $implicit: item, index }
)
);
});
}
}
Usage
@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' },
...
];
}