Angular 创建结构指令

我们将创建一个延迟组件或元素实例化的appDelay结构指令。 这可以潜在地用于美化效果或用用于手动处理组件加载的时间,无论是性能还是UX。

@Directive({
  selector: '[appDelay]'
})
export class DelayDirective {
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainerRef: ViewContainerRef
  ) { }
  @Input()
  set appDelay(time: number): void { }
}

View Example

我们使用相同的@Directive类装饰器作为属性指令,并以相同的方式定义一个选择器。 这里的一个很大的区别是,由于结构指令的性质绑定到模板,我们可以访问TemplateRef,一个表示指令附加的template标签的对象。 我们还以类似的方式添加一个输入属性,但这次使用一个set处理程序,所以我们可以在Angular 2执行绑定时执行一些代码。 我们以与Angular 2内置结构指令完全相同的方式绑定delay

@Component({
  selector: 'app-root',
  template: `
    <div *ngFor="let item of [1,2,3,4,5,6]">
      <card *delay="500 * item">
        {{item}}
      </card>
    </div>
  `
})
export class AppComponent {
}

View Example

注意,没有内容在渲染。 这是由于Angular 2模拟HTML模板标签,并且默认情况下不渲染任何子元素。 为了能够渲染这个内容,我们必须将TemplateRef给出的模板作为嵌入视图附加到视图容器。

查看容器和嵌入视图

查看容器是可以附加一个或多个视图的容器。 视图表示要渲染的某种布局和渲染它的上下文。 视图容器锚定到组件,并负责生成其输出,因此这意味着更改附加到视图容器的视图会影响组件的最终渲染输出。

可以将两种类型的视图附加到视图容器:链接到组件的主机视图,以及链接到模板的嵌入视图。 由于结构指令与模板交互,我们有兴趣在这种情况下使用嵌入视图。

import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
@Directive({
  selector: '[delay]'
})
export class DelayDirective {
  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainerRef: ViewContainerRef
  ) { }
  @Input('delay')
  set delayTime(time: number): void {
    setTimeout(
      () => {
        this.viewContainerRef.createEmbeddedView(this.templateRef);
      },
      time);
  }
}

查看示例 指令通过注入ViewContainerRef访问视图容器。 嵌入视图通过调用ViewContainerRef的createEmbeddedView方法并传入模板来创建并附加到视图容器。 我们想使用我们的指令附加的模板,所以我们传入注入的TemplateRef。

向指令提供上下文变量

假设我们要记录一些关于我们的指令如何影响组件的元数据,并使这些数据可用。 例如,在我们的appDelay 指令中,我们进行了一个setTimeout调用,在JavaScript的单线程异步模型中意味着它可能不会在我们提供的确切时间之后运行。 我们将捕获它确切的加载时间,并使该变量在模板中可用。

export class DelayContext {
  constructor(private loadTime: number) { }
}
@Directive({
  selector: '[appDelay]'
})
export class DelayDirective {
  constructor(
    private templateRef: TemplateRef<DelayContext>,
    private viewContainerRef: ViewContainerRef
  ) { }
  @Input()
  set appDelay(time: number): void {
    setTimeout(
      () => {
        this.viewContainerRef.createEmbeddedView(
          this.templateRef,
          new DelayContext(performance.now())
        );
      },
      time);
  }
}

View Example

我们对appDelay 指令做了一些修改。 我们创建了一个新的DelayContext类,它包含我们想要提供给我们的指令的上下文。 在这种情况下,我们要捕获createEmbeddedView调用发生的实际时间,并使其在我们的指令中可用作loadTime。 我们还提供了我们的新类作为TemplateRef函数的泛型参数。 这使得静态分析,并让我们确保我们的调用createEmbeddedView传递类型DelayContext的变量。 在我们的createEmbeddedView调用中,我们传入我们的变量,它捕获了方法调用的时间。

在使用appDelay 的组件中,我们以访问ngFor中的变量的方式访问loadTime上下文变量。

@Component({
  selector: 'app-root',
  template: `
    <div *ngFor="let item of [1,2,3,4,5,6]">
      <card *delay="500 * item; let loaded = loadTime">
        <div class="main">{{item}}</div>
        <div class="sub">{{loaded | number:'1.4-4'}}</div>
      </card>
    </div>
  `
})

查看示例