Skip to content

angular实现类似Vue中的slot用法

 at 03:07(Updated)

方案一: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' },
    ...
  ];
}