Angular 2 ViewChild & ViewChildren

30
阅读 Angular 6/RxJS 最新教程,请访问前端修仙之路

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


如果觉得我的文章对你有用,请随意赞赏
已赞赏

你可能感兴趣的

13 条评论
Keriy · 2017年06月20日

后面看不懂了

回复

0

后面代码是基于2.x的,4.x代码很简洁,看不懂直接略过哈!

semlinker 作者 · 2017年06月20日
0

受伤了。。。

Keriy · 2017年06月20日
慢慢磨成针 · 2017年06月22日

@ViewChild('tpl', { read: ViewContainerRef })
tplVcRef: ViewContainerRef;
这个我在官网也没读懂什么意思。
是不是第一句加read: ViewContainerRef
第二句就直接写tplVcRef不用重复写ViewContainerRef的意思

回复

0

如果没有设置 {read: ''} 查询条件,默认获取的就是 TemplateRef 对象哈

semlinker 作者 · 2017年06月22日
0

哦原来如此,明白了

慢慢磨成针 · 2017年06月22日
0

没看明白啊。。为什么我看到的ViewContainerRef 都是放在constructor

learnmeahaskell · 2017年10月11日
悄悄 · 2017年12月07日

当我使用<router-outlet>路由形式的时候,用 @ViewChild就取不到子组件是吗?
显示undefined

回复

0

你解决了吗,我也遇到这个问题

圆伞科技 · 2018年08月07日
0

可以获取到的,只是这个值被设置的时间比较奇怪,在AfterViewInit内获取仍然是null。
我把当前对象输出到console中查看是有值的,说明设置时间是在AfterViewInit之后;由于没有其它生命周期方法可用了。我目前的解决方案是 写一个函数用setTimeout反复轮询到有值为止,不知道还有没有其它解决方案

熊猫伸伸腿 · 2018年10月10日
白面葫芦娃 · 2018年03月12日

makeMetadataCtor这个函数,那个args不是一直都是空数组么,那么它的args[i]不应该都是undefined么。。
for (var propName in prop) {

                    _this[propName] = 
                        argVal && argVal.hasOwnProperty(propName) ? 
                      argVal[propName] : prop[propName];
                }

这段代码_this[propName]不都是=argVal了么。
还是我哪里没看懂。。

回复

全真教丿尹志平 · 2018年06月09日

ViewChildDecorator 之后我都没有看明白,有点复杂

回复

huangyuanzhen · 2018年07月31日

viewChild引进的模板的值,不一定非得子在ngAfterViewInit 钩子函数里面使用吧

回复

载入中...