Angular Custom Slots

Updated:

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' },
    ...
  ];
}

Practices


Share this post on:

Next Post
Potree - Get all points in pointcloud