Angular 2 ViewChild & ViewChildren

轉自

ViewChild

ViewChild 是屬性裝飾器,用來從模板視圖中獲取匹配的元素。視圖查詢在 ngAfterViewInit 鉤子函數調用前完成,因此在 ngAfterViewInit 鉤子函數中,就能正確獲取查詢的元素。

@ViewChild 使用模板變量名

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

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

  @ViewChild('greet')
  greetDiv: ElementRef;

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

@ViewChild 使用模板變量名及設置查詢條件

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 {

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

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

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

@ViewChild 使用類型查詢

child.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'exe-child',
    template: `
      <p>Child Component</p>  
    `
})
export class ChildComponent {
    name: string = 'child-component';
}

app.component.ts

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-child></exe-child>
  `,
})
export class AppComponent {

  @ViewChild(ChildComponent)
  childCmp: ChildComponent;

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

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

在這裏插入圖片描述

ViewChildren

ViewChildren 用來從模板視圖中獲取匹配的多個元素,返回的結果是一個 QueryList 集合。

@ViewChildren 使用類型查詢

import { Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'my-app',
  template: `
    <h4>Welcome to Angular World</h4>
    <exe-child></exe-child>
    <exe-child></exe-child>
  `,
})
export class AppComponent {

  @ViewChildren(ChildComponent)
  childCmps: QueryList<ChildComponent>;

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

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

在這裏插入圖片描述

ViewChild 詳解

@ViewChild 示例

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

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <p #greet>Hello {{ name }}</p>
  `,
})
export class AppComponent {
  name: string = 'Semlinker';
  @ViewChild('greet')
  greetDiv: ElementRef;
}

編譯後的 ES5 代碼片段

var core_1 = require('@angular/core');

var AppComponent = (function () {
    function AppComponent() {
        this.name = 'Semlinker';
    }
    __decorate([
        core_1.ViewChild('greet'), // 設定selector爲模板變量名
        __metadata('design:type', core_1.ElementRef)
], AppComponent.prototype, "greetDiv", void 0);

ViewChildDecorator 接口

export interface ViewChildDecorator {
  // Type類型:@ViewChild(ChildComponent)
  // string類型:@ViewChild('tpl', { read: ViewContainerRef })
  (selector: Type<any>|Function|string, {read}?: {read?: any}): any;

  new (selector: Type<any>|Function|string, 
      {read}?: {read?: any}): ViewChild;
}

ViewChildDecorator

export const ViewChild: ViewChildDecorator = makePropDecorator(
    'ViewChild',
    [
      ['selector', undefined],
      {
        first: true,
        isViewQuery: true,
        descendants: true,
        read: undefined,
      }
    ],
Query);

makePropDecorator函數片段

/*
 * 創建PropDecorator工廠
 * 
 * 調用 makePropDecorator('ViewChild', [...]) 後返回ParamDecoratorFactory
 */
function makePropDecorator(name, props, parentClass) {
          // name: 'ViewChild'
          // props: [['selector', undefined], 
          //  { first: true, isViewQuery: true, descendants: true, read: undefined}]
  
          // 創建Metadata構造函數
        var metaCtor = makeMetadataCtor(props);
      
        function PropDecoratorFactory() {
            var args = [];
               ... // 轉換arguments對象成args數組
            if (this instanceof PropDecoratorFactory) {
                metaCtor.apply(this, args);
                return this;
            }
            ...
            return function PropDecorator(target, name) {
                var meta = Reflect.getOwnMetadata('propMetadata', 
                    target.constructor) || {};
                meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
                meta[name].unshift(decoratorInstance);
                Reflect.defineMetadata('propMetadata', meta, target.constructor);
            };
            var _a;
        }
           if (parentClass) { // parentClass: Query
            PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
        }
          ...
        return PropDecoratorFactory;
    }

makeMetadataCtor 函數:

// 生成Metadata構造函數: var metaCtor = makeMetadataCtor(props); 
// props: [['selector', undefined], 
// { first: true, isViewQuery: true, descendants: true, read: undefined }]
  function makeMetadataCtor(props) {
        // metaCtor.apply(this, args);
        return function ctor() {
            var _this = this;
            var args = [];
            ... // 轉換arguments對象成args數組
            props.forEach(function (prop, i) { // prop: ['selector', undefined]
                var argVal = args[i]; 
                if (Array.isArray(prop)) { // argVal: 'greet'
                    _this[prop[0]] = argVal === undefined ? prop[1] : argVal;
                }
                else {
             // { first: true, isViewQuery: true, descendants: true, read: undefined }
             // 合併用戶參數與默認參數,設置read屬性值     
                    for (var propName in prop) { 
                        _this[propName] = 
                            argVal && argVal.hasOwnProperty(propName) ? 
                          argVal[propName] : prop[propName];
                    }
                }
            });
        };
}

我們可以在控制檯輸入 window[‘core-js_shared’] ,查看通過 Reflect API 保存後的metadata信息
在這裏插入圖片描述

接下來我們看一下編譯後的 component.ngfactory.js 代碼片段,查詢條件 @ViewChild(‘greet’)

在這裏插入圖片描述

我們再來看一下前面示例中,編譯後 component.ngfactory.js 代碼片段,查詢條件分別爲:

1.@ViewChild(‘tpl’, { read: ViewContainerRef })

在這裏插入圖片描述

2.@ViewChild(ChildComponent)

在這裏插入圖片描述

通過觀察不同查詢條件下,編譯生成的 component.ngfactory.js 代碼片段,我們發現 Angular 在創建 AppComponent 實例後,會自動調用 AppComponent 原型上的 createInternal 方法,纔開始創建組件中元素,所以之前我們在構造函數中是獲取不到通過 ViewChild 裝飾器查詢的視圖元素。另外,配置的視圖查詢條件,默認都會創建一個 jit_QueryList 對象,然後根據 read 查詢條件,創建對應的實例對象,然後添加至 QueryList 對象中,然後在導出對應的查詢元素到組件對應的屬性中。

總結

ViewChild 裝飾器用於獲取模板視圖中的元素,它支持 Type 類型或 string 類型的選擇器,同時支持設置 read 查詢條件,以獲取不同類型的實例。而 ViewChildren 裝飾器是用來從模板視圖中獲取匹配的多個元素,返回的結果是一個 QueryList 集合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章