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 集合。