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,ViewContainerRef,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,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,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,ViewChild } from '@angular/core'; @Component({ selector: 'my-app',}) 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',// { first: 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; }
// 生成Metadata构造函数: var MetaCtor = makeMetadataCtor(props); // props: [['selector',// { first: 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,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 集合。