Angular 2 TemplateRef & ViewContainerRef

閱讀 Angular 6/RxJS 最新教程,請訪問 前端修仙之路

TemplateRef

在介紹 TemplateRef 前,咱們先來了解一下 HTML 模板元素 - <template> 。模板元素是一種機制,容許包含加載頁面時不渲染,但又能夠隨後經過 JavaScript 進行實例化的客戶端內容。咱們能夠將模板視做爲存儲在頁面上稍後使用的一小段內容。javascript

在 HTML5 標準引入 template 模板元素以前,咱們都是使用 <script> 標籤進行客戶端模板的定義,具體以下:html

<script id="tpl-mock" type="text/template">
   <span>I am span in mock template</span>
</script>

對於支持 HTML5 template 模板元素的瀏覽器,咱們能夠這樣建立客戶端模板:前端

<template id="tpl">
    <span>I am span in template</span>
</template>

下面咱們來看一下 HTML5 template 模板元素的使用示例:java

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"> <title>HTML5 Template Element Demo</title></head>
<body>
<h4>HTML5 Template Element Demo</h4>
<!-- Template Container -->
<div class="tpl-container"></div>
<!-- Template -->
<template id="tpl">
    <span>I am span in template</span>
</template>
<!-- Script -->
<script type="text/javascript">
    (function renderTpl() {
        if ('content' in document.createElement('template')) {
            var tpl = document.querySelector('#tpl');
            var tplContainer = document.querySelector('.tpl-container');
            var tplNode = document.importNode(tpl.content, true);
            tplContainer.appendChild(tplNode); 
        } else {
            throw  new Error("Current browser doesn't support template element");
        }
    })();
</script>
</body>
</html>

以上代碼運行後,在瀏覽器中咱們會看到如下內容:node

HTML5 Template Element Demo

I am span in template

而當咱們註釋掉 tplContainer.appendChild(tplNode) 語句時,刷新瀏覽器後看到的是:segmentfault

HTML5 Template Element Demo

這說明頁面中 <template> 模板元素中的內容,若是沒有進行處理對用戶來講是不可見的。Angular 2 中,<template> 模板元素主要應用在結構指令中,此外在 Angular 2 屬性指令 vs 結構指令 文章中咱們也介紹了 <template> 模板元素和自定義結構指令,接下來咱們先來介紹一下本文中的第一個主角 - TemplateRef:瀏覽器

import {Component, TemplateRef, ViewChild, AfterViewInit} from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <template #tpl>
      <span>I am span in template</span>
    </template>
  `,
})
export class AppComponent {
  name: string = 'Semlinker';

  @ViewChild('tpl')
  tpl: TemplateRef<any>;

  ngAfterViewInit() {
    console.dir(this.tpl);
  }
}

上述代碼運行後的控制檯的輸出結果以下:app

圖片描述

從上圖中,咱們發現 @Component template 中定義的 <template> 模板元素,渲染後被替換成 comment 元素,其內容爲 "template bindings={}" 。此外咱們經過 @ViewChild 獲取的模板元素,是 TemplateRef_ 類的實例,接下來咱們來研究一下 TemplateRef_ 類:this

TemplateRef_spa

// @angular/core/src/linker/template_ref.d.ts
export declare class TemplateRef_<C> extends TemplateRef<C> {
    private _parentView;
    private _nodeIndex;
    private _nativeElement;
    constructor(_parentView: AppView<any>, _nodeIndex: number, _nativeElement: any);
    createEmbeddedView(context: C): EmbeddedViewRef<C>;
    elementRef: ElementRef;
}

TemplateRef

// @angular/core/src/linker/template_ref.d.ts
// 用於表示內嵌的template模板,可以用於建立內嵌視圖(Embedded Views)
export declare abstract class TemplateRef<C> {
    elementRef: ElementRef;
    abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
}

(備註:抽象類與普通類的區別是抽象類有包含抽象方法,不能直接實例化抽象類,只能實例化該抽象類的子類)

咱們已經知道 <template> 模板元素,渲染後被替換成 comment 元素,那麼應該如何顯示咱們模板中定義的內容呢 ?咱們注意到了 TemplateRef 抽象類中定義的 createEmbeddedView
抽象方法,該方法的返回值是 EmbeddedViewRef 對象。那好咱們立刻來試一下:

import {Component, TemplateRef, ViewChild, AfterViewInit} from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <template #tpl>
      <span>I am span in template</span>
    </template>
  `,
})
export class AppComponent {
  name: string = 'Semlinker';

  @ViewChild('tpl')
  tpl: TemplateRef<any>;

  ngAfterViewInit() {
    let embeddedView = this.tpl.createEmbeddedView(null);
    console.dir(embeddedView);
  }
}

上述代碼運行後的控制檯的輸出結果以下:

圖片描述

從圖中咱們能夠知道,當調用 createEmbeddedView 方法後返回了 ViewRef_ 視圖對象。該視圖對象的 rootNodes 屬性包含了 <template> 模板中的內容。在上面的例子中,咱們知道了 TemplateRef 實例對象中的 elementRef 屬性封裝了咱們的 comment 元素,那麼咱們能夠經過 insertBefore 方法來建立咱們模板中定義的內容。

import { Component, TemplateRef, ViewChild, AfterViewInit } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <template #tpl>
      <span>I am span in template {{name}}</span>
    </template>
  `,
})
export class AppComponent {
  name: string = 'Semlinker';

  @ViewChild('tpl')
  tpl: TemplateRef<any>;

  ngAfterViewInit() {
    // 頁面中的<!--template bindings={}-->元素
    let commentElement = this.tpl.elementRef.nativeElement;
    // 建立內嵌視圖
    let embeddedView = this.tpl.createEmbeddedView(null);
    // 動態添加子節點
    embeddedView.rootNodes.forEach((node) => {
        commentElement.parentNode
          .insertBefore(node, commentElement.nextSibling);
    });
  }
}

成功運行上面的代碼後,在瀏覽器中咱們會看到如下內容:

Welcome to Angular World

I am span in template

如今咱們來回顧一下,上面的處理步驟:

  • 建立內嵌視圖(embedded view)
  • 遍歷內嵌視圖中的 rootNodes,動態的插入 node

雖然咱們已經成功的顯示出 template 模板元素中的內容,但發現整個流程仍是太複雜了,那有沒有簡單地方式呢 ?是時候介紹本文中第二個主角 - ViewContainerRef。

ViewContainerRef

咱們先來檢驗一下它的能力,而後再來好好地分析它。具體示例以下:

import { Component, TemplateRef, ViewChild, ViewContainerRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <template #tpl>
      <span>I am span in template</span>
    </template>
  `,
})
export class AppComponent {
  name: string = 'Semlinker';

  @ViewChild('tpl')
  tplRef: TemplateRef<any>;

  @ViewChild('tpl', { read: ViewContainerRef })
  tplVcRef: ViewContainerRef;

  ngAfterViewInit() {
    // console.dir(this.tplVcRef); (1)
    this.tplVcRef.createEmbeddedView(this.tplRef);
  }
}

移除上面代碼中的註釋,便可在控制檯看到如下的輸出信息:

圖片描述

而在瀏覽器中咱們會看到如下內容:

Welcome to Angular World

I am span in template

接下來咱們來看一下 ViewContainerRef_ 類:

// @angular/core/src/linker/view_container_ref.d.ts
// 用於表示一個視圖容器,可添加一個或多個視圖
export declare class ViewContainerRef_ implements ViewContainerRef {
    ...
    length: number; // 返回視圖容器中已存在的視圖個數
    element: ElementRef;
    injector: Injector;
    parentInjector: Injector;
      // 基於TemplateRef建立內嵌視圖,並自動添加到視圖容器中,可經過index設置
    // 視圖添加的位置
    createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, 
      index?: number): EmbeddedViewRef<C>;
    // 基 ComponentFactory建立組件視圖
    createComponent<C>(componentFactory: ComponentFactory<C>,
      index?: number, injector?: Injector, projectableNodes?: any[][]): ComponentRef<C>;
    insert(viewRef: ViewRef, index?: number): ViewRef;
    move(viewRef: ViewRef, currentIndex: number): ViewRef;
    indexOf(viewRef: ViewRef): number;
    remove(index?: number): void;
    detach(index?: number): ViewRef;
    clear(): void;
}

經過源碼咱們能夠知道經過 ViewContainerRef_ 實例,咱們能夠方便地操做視圖,也能夠方便地基於 TemplateRef 建立視圖。如今咱們來總結一下 TemplateRef 與 ViewContainerRef。

TemplateRef:用於表示內嵌的 template 模板元素,經過 TemplateRef 實例,咱們能夠方便建立內嵌視圖(Embedded Views),且能夠輕鬆地訪問到經過 ElementRef 封裝後的 nativeElement。須要注意的是組件視圖中的 template 模板元素,通過渲染後會被替換成 comment 元素。

ViewContainerRef:用於表示一個視圖容器,可添加一個或多個視圖。經過 ViewContainer
Ref 實例,咱們能夠基於 TemplateRef 實例建立內嵌視圖,並能指定內嵌視圖的插入位置,也能夠方便對視圖容器中已有的視圖進行管理。簡而言之,ViewContainerRef 的主要做用是建立和管理內嵌視圖或組件視圖。

我有話說

1.Angular 2 支持的 View(視圖) 類型有哪幾種 ?

  • Embedded Views - Template 模板元素
  • Host Views - Component 組件

1.1 如何建立 Embedded View

ngAfterViewInit() {
    let view = this.tpl.createEmbeddedView(null);
}

1.2 如何建立 Host View

constructor(private injector: Injector,
    private r: ComponentFactoryResolver) {
    let factory = this.r.resolveComponentFactory(AppComponent);
    let componentRef = factory.create(injector);
    let view = componentRef.hostView;
}

2.Angular 2 Component 組件中定義的 <template> 模板元素爲何渲染後會被移除 ?

由於 <template> 模板元素,已經被 Angular 2 解析並封裝成 TemplateRef 實例,經過 TemplateRef 實例,咱們能夠方便地建立內嵌視圖(Embedded View),咱們不須要像開篇中的例子那樣,手動操做 <template> 模板元素。

3.ViewRef 與 EmbeddedViewRef 之間有什麼關係 ?

ViewRef 用於表示 Angular View(視圖),視圖是可視化的 UI 界面。EmbeddedViewRef 繼承於 ViewRef,用於表示 <template> 模板元素中定義的 UI 元素。

ViewRef

// @angular/core/src/linker/view_ref.d.ts
export declare abstract class ViewRef {
    destroyed: boolean;
    abstract onDestroy(callback: Function): any;
}

EmbeddedViewRef

// @angular/core/src/linker/view_ref.d.ts
export declare abstract class EmbeddedViewRef<C> extends ViewRef {
    context: C;
    rootNodes: any[]; // 保存<template>模板中定義的元素
    abstract destroy(): void; // 用於銷燬視圖
}

總結

Angular 2 中 TemplateRef 與 ViewContainerRef 的概念對於初學者來講會比較羞澀難懂,本文從基本的 HTML 5 <template> 模板元素開始,介紹瞭如何操做和應用頁面中定義的模板。而後經過實例介紹了 Angular 2 中 TemplateRef 和 ViewContainerRef 的定義和做用。但願經過這篇文章,讀者能更好的理解 TemplateRef 與 ViewContainerRef。

相關文章
相關標籤/搜索