1. 程式人生 > >Angular 正確使用 @ViewChild、@ViewChildren 訪問 DOM、元件、指令

Angular 正確使用 @ViewChild、@ViewChildren 訪問 DOM、元件、指令

       @ViewChild和@ViewChildren是Angular提供給我們的裝飾器,用於從模板檢視中獲取匹配的元素。需要注意的是@ViewChild和@ViewChildren會在父元件鉤子方法ngAfterViewInit呼叫之前賦值。接下來我們來看下@ViewChild、@ViewChildren怎麼使用。

一 選擇器

       @ViewChild、@ViewChildren支援的選擇器包括以下幾種(@ViewChild、@ViewChildren的引數有以下幾種):

1.1 class

       當然了這個class也是有條件的,必須是@Component或者@Directive修飾的clas。

1.2 模板應用變數。

       得解釋下什麼是模板應用變數。直接用一個例子來說明。
例如<my-component #cmp>裡面的cmp就是模板應用變數。

1.3 子元件provider提供的類

       我們還是用實際的例子來說明,比如我們有一個子元件。程式碼如下

import {Component, OnInit} from '@angular/core';
import {ChildService} from './child.service';

@Component({
  selector: 'app-child',
  template: `
    <h1>自定義的一個子元件</h1>
  `,
  providers: [
    ChildService
  ]
})
export class ChildComponent implements OnInit {

  constructor(public childService: ChildService) {
  }

  ngOnInit() {
  }

}

       在上面的子元件裡面provider裡面提供了一個ChildService類。我們也是可以通過@ViewChild來拿到這個ChildService類的。程式碼如下

  @ViewChild(ChildService) childService: ChildService;

1.4 子元件provider通過string token提供的類

       我們還是用實際的例子來說明。子元件的pprovider通過 string token valued的形式提供了一個StringTokenValue類,string token 對應的是tokenService。

import {Component} from '@angular/core';
import {StringTokenValue} from './string-token-value';

@Component({
  selector: 'app-child',
  template: `
    <h1>自定義的一個子元件</h1>
  `,
  styleUrls: ['./child.component.less'],
  providers: [
    {provide: 'tokenService', useClass: StringTokenValue}
  ]
})
export class ChildComponent {


}

       在父元件裡面我們也是可以拿到子元件provider的這個StringTokenValue類的。方式如下:

  @ViewChild('tokenService') tokenService: StringTokenValue;

1.5 TemplateRef

       當選擇器是TemplateRef的時候,則會獲取到html裡面所有的ng-template的節點。實際例子如下:

  /**** @ViewChild(TemplateRef) @ViewChildren(TemplateRef)獲取頁面上的ng-template節點資訊 ****/
  @ViewChild(TemplateRef) template: TemplateRef<any>;
  @ViewChildren(TemplateRef) templateList: QueryList<TemplateRef<any>>;

二 使用場景

2.1 DOM

       如果想通過@ViewChild、@ViewChildren訪問到DOM節點的話都得先給相應的DOM節點設定模板應用變數。例如下面的domLabel就是一個模板應用變數。

<div #domLabel>cba</div>

2.1.1 獲取DOM裡面宿主檢視

       咱們可以簡單把div,input,label等等,當然了還包括我們自定義的一些元件都認為是宿主檢視。用ElementRef變數接收到獲取到的宿主檢視元素。

ElementRef

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

@Component({
  selector: 'app-root',
  template: `
    <!-- view child dom -->
    <div #domLabel>
      abc
    </div>
  `,
  styleUrls: ['./app.component.less']
})
export class AppComponent implements AfterViewInit {
  /**** DOM節點對應的 ****/
    // DOM節點只能使用模板應用變數來找到
  @ViewChild('domLabel') domLabelChild: ElementRef; // 找到第一個符合條件的節點

  ngAfterViewInit(): void {
    // DOM節點
    console.log(this.domLabelChild.nativeElement);
  }
}

2.1.2 獲取DOM裡面嵌入檢視

       咱們可以簡單的吧 ng-template的內容認為是嵌入檢視。

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

@Component({
  selector: 'app-root',
  template: `
    <ng-template #domTemplate>
      <div>預設我是不會顯示的</div>
    </ng-template>
    <ng-template>
      <div></div>
    </ng-template>
  `,
  styleUrls: ['./app.component.less']
})
export class AppComponent implements AfterViewInit {
  /**** DOM節點對應的 ****/
  @ViewChild('domTemplate') domTemplate: TemplateRef<any>; // 查詢嵌入元素
  /**** @ViewChild(TemplateRef) @ViewChildren(TemplateRef)獲取頁面上的ng-template節點資訊 ****/
  @ViewChild(TemplateRef) template: TemplateRef<any>;
  @ViewChildren(TemplateRef) templateList: QueryList<TemplateRef<any>>;

  ngAfterViewInit(): void {
    // DOM節點
    console.log(this.domTemplate);
    console.log(this.template);
    if (this.templateList != null && this.templateList.length !== 0) {
      this.templateList.forEach(elementRef => console.log(elementRef));
    }
  }
}

2.2 元件

       大部分情況下第三方庫裡面的元件或者我們自定義的一些元件在父元件裡面都是需要拿到這個子元件物件的。有兩種獲取獲取到元件:一個是通過模板變數名、另一個是通過元件類。

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

@Component({
  selector: 'app-root',
  template: `
    <!-- view child for Component -->
    <app-child #appChild></app-child>
    <app-child></app-child>
  `,
  styleUrls: ['./app.component.less']
})
export class AppComponent implements AfterViewInit {
  /**** 元件 ****/
  @ViewChild('appChild') componentChild: ChildComponent; // 通過模板變數名獲取
  @ViewChild(ChildComponent) componentChildByType: ChildComponent; // 直接通過元件型別獲取
  @ViewChild('appChild', {read: ElementRef}) componentChildElement: ElementRef; // 直接找到子元件對應的DOM
  @ViewChildren(ChildComponent) componentChildList: QueryList<ChildComponent>; // 獲取所有的

  ngAfterViewInit(): void {
    // DOM節點
    console.log(this.componentChild);
    if (this.componentChildList != null && this.componentChildList.length !== 0) {
      this.componentChildList.forEach(elementRef => console.log(elementRef));
    }
  }
}

       有寫場景我可能需要直接獲取到元件對應的DOM節點,這個時候
@ViewChild、@ViewChildren就的加上過濾條件{read: ElementRef}。如下所示:

  @ViewChild('appChild', {read: ElementRef}) componentChildElement: ElementRef; // 直接找到子元件對應的DOM

2.3 指令

       @ViewChild、@ViewChildren也是可以獲取到指令物件的。指令物件的獲取和元件物件的獲取差不多,唯一不同的地方就是用模板變數名獲取指令的時候要做一些特殊的處理。我們還是用具體的例項來說明。

       我們自定義一個非常簡單的指令TestDirective,新增exportAs屬性。程式碼如下。

exportAs屬性很關鍵

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

/**
 * 指令,測試使用,這裡使用了exportAs,就是為了方便我們精確的找到指令
 */
@Directive({
  selector: '[appTestDirective]',
  exportAs: 'appTest'
})
export class TestDirective {

  constructor(private elementRef: ElementRef) {
    elementRef.nativeElement.value = '我添加了指令';
  }

}

       獲取TestDirective指令,注意單個指令物件獲取的時候,模板變數名的寫法。比如下面的程式碼中#divTestDirective=‘appTest’,模板變數名等號右邊的就是TestDirective指令exportAs對應的名字。

import {AfterViewInit, Component, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {TestDirective} from './test.directive';

@Component({
  selector: 'app-root',
  template: `
    <!-- view child for Directive -->
    <input appTestDirective/>
    <br/>
    <input appTestDirective #divTestDirective='appTest'/>
  `,
  styleUrls: ['./app.component.less']
})
export class AppComponent implements AfterViewInit {
  /**
   * 獲取html裡面所有的TestDirective
   */
  @ViewChildren(TestDirective) testDirectiveList: QueryList<TestDirective>;
  /**
   * 獲取模板變數名為divTestDirective的TestDirective的指令,這個得配合指令的exportAs使用
   */
  @ViewChild('divTestDirective') testDirective: TestDirective;

  ngAfterViewInit(): void {
    console.log(this.testDirective);
    if (this.testDirectiveList != null && this.testDirectiveList.length !== 0) {
      this.testDirectiveList.forEach(elementRef => console.log(elementRef));
    }
  }
}


       例項連結地址

       總結:@ViewChild、@ViewChildren獲取子元素的的時候,我們用的最多的應該就是通過模板變數名,或者直接通過class來獲取了。還有一個特別要注意的地方就是獲取單個指令物件的時候需要配合指令的exportAs屬性使用,並且把他賦值給模板變數名。