Angular 4 ElementRef

Angular 的口號是 - "一套框架,多種平臺。同時適用手機與桌面 (One framework.Mobile & desktop.)",即 Angular 是支持開發跨平臺的應用,好比:Web 應用、移動 Web 應用、原生移動應用和原生桌面應用等。node

爲了可以支持跨平臺,Angular 經過抽象層封裝了不一樣平臺的差別,統一了 API 接口。如定義了抽象類 Renderer 、抽象類 RootRenderer 等。此外還定義瞭如下引用類型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。下面咱們就來分析一下 ElementRef 類:web

ElementRef的做用

在應用層直接操做 DOM,就會形成應用層與渲染層之間強耦合,致使咱們的應用沒法運行在不一樣環境,如 web worker 中,由於在 web worker 環境中,是不能直接操做 DOM。有興趣的讀者,能夠閱讀一下 Web Workers 中支持的類和方法 這篇文章。經過 ElementRef 咱們就能夠封裝不一樣平臺下視圖層中的 native 元素 (在瀏覽器環境中,native 元素一般是指 DOM 元素),最後藉助於 Angular 提供的強大的依賴注入特性,咱們就能夠輕鬆地訪問到 native 元素。segmentfault

ElementRef的定義

export class ElementRef {
  public nativeElement: any;
  constructor(nativeElement: any) { this.nativeElement = nativeElement; }
}

ElementRef的應用

咱們先來介紹一下總體需求,咱們想在頁面成功渲染後,獲取頁面中的 div 元素,並改變該 div 元素的背景顏色。接下來咱們來一步步,實現這個需求。瀏覽器

首先咱們要先獲取 div 元素,在文中 "ElementRef 的做用" 部分,咱們已經提到能夠利用 Angular 提供的強大的依賴注入特性,獲取封裝後的 native 元素。在瀏覽器中 native 元素就是 DOM 元素,咱們只要先獲取 my-app元素,而後利用 querySelector API 就能獲取頁面中 div 元素。具體代碼以下:app

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

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <div>Hello {{ name }}</div>
  `,
})
export class AppComponent {

  name: string = 'Semlinker';

  constructor(private elementRef: ElementRef) {
    let divEle = this.elementRef.nativeElement.querySelector('div');
    console.dir(divEle);
  }
}

運行上面代碼,在控制檯中沒有出現異常,可是輸出的結果倒是 null 。什麼狀況 ? 沒有拋出異常,咱們能夠推斷 this.elementRef.nativeElement 這個對象是存在,但卻找不到它的子元素,那應該是在調用構造函數的時候,my-app 元素下的子元素還未建立。那怎麼解決這個問題呢 ?沉思中… ,不是有 setTimeout 麼,咱們在稍微改造一下:框架

constructor(private elementRef: ElementRef) {
  setTimeout(() => { // 此處須要使用箭頭函數哈,你懂的...
      let divEle = this.elementRef.nativeElement.querySelector('div');
      console.dir(divEle);
   }, 0);
}

更新一下代碼,此時控制檯成功輸出了 div 。爲何添加個 setTimeout 就能成功獲取到想要的 div 元素呢?此處就不展開了,有興趣的讀者能夠參考 - What the heck is the event loop anyway? 這個演講的示例。函數

問題解決了,但感受不是很優雅 ?有沒有更好的方案,答案是確定的。Angular 不是有提供組件生命週期的鉤子,咱們能夠選擇一個合適的時機,而後獲取咱們想要的 div 元素。oop

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

@Component({
  selector: 'my-app',
  template: `
    <h1>Welcome to Angular World</h1>
    <div>Hello {{ name }}</div>
  `,
})
export class AppComponent {

  name: string = 'Semlinker';

  // 在構造函數中 this.elementRef = elementRef 是可選的,編譯時會自動賦值
  // function AppComponent(elementRef) { this.elementRef = elementRef; }
  constructor(private elementRef: ElementRef) { } 

  ngAfterViewInit() { // 模板中的元素已建立完成
    console.dir(this.elementRef.nativeElement.querySelector('div'));
    // let greetDiv: HTMLElement = this.elementRef.nativeElement.querySelector('div'); 
    // greetDiv.style.backgroundColor = 'red';
  }
}

運行一下上面的代碼,咱們看到了意料中的 div 元素。咱們直接選用 ngAfterViewInit 這個鉤子,不要問我爲何,由於它看得最順眼咯。不過咱們後面也會有專門的文章,詳細分析一下 Angular 組件的生命週期。成功取到 div 元素,就剩下的事情就好辦了,直接經過 style 對象設置元素的背景顏色。優化

功能雖然已經實現了,但還有優化的空間麼?在 Angular 2 Decorators part - 2 文章中咱們有談到 Angular 內置的屬性裝飾器,如 @ContentChild、 @ContentChildren、@ViewChild、@ViewChildren 等。相信讀者看完後,已經猜到咱們的優化方案了。具體示例以下:this

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

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

  @ViewChild('greet')
  greetDiv: ElementRef;

  ngAfterViewInit() {
    this.greetDiv.nativeElement.style.backgroundColor = 'red';
  }
}

是否是感受瞬間高大上了,不過先等等,上面的代碼是否是還有進一步的優化空間呢 ?咱們看到設置 div 元素的背景,咱們是默認應用的運行環境在是瀏覽器中。前面已經介紹了,咱們要儘可能減小應用層與渲染層之間強耦合關係,從而讓咱們應用可以靈活地運行在不一樣環境。最後咱們來看一下,最終優化後的代碼:

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

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

  @ViewChild('greet')
  greetDiv: ElementRef;

  constructor(private elementRef: ElementRef, private renderer: Renderer) { }

  ngAfterViewInit() {
    // this.greetDiv.nativeElement.style.backgroundColor  = 'red';
    this.renderer.setElementStyle(this.greetDiv.nativeElement, 'backgroundColor', 'red');
  }
}

最後咱們經過 renderer 對象提供的 API 優雅地設置了 div 元素的背景顏色。

我有話說

1.Renderer API 還有哪些經常使用的方法 ?

export abstract class Renderer {
  // 建立元素
  abstract createElement(parentElement: any, name: string, 
      debugInfo?: RenderDebugInfo): any;
  
  // 建立文本元素
  abstract createText(parentElement: any, value: string, 
      debugInfo?: RenderDebugInfo): any;
      
  // 設置文本
  abstract setText(renderNode: any, text: string): void;
      
  // 設置元素Property
  abstract setElementProperty(renderElement: any, propertyName: string, 
      propertyValue: any): void;
      
  // 設置元素Attribute
  abstract setElementAttribute(renderElement: any, attributeName: string, 
      attributeValue: string): void;    
      
  // 設置元素的Class
  abstract setElementClass(renderElement: any, className: string,
      isAdd: boolean): void;    
      
  // 設置元素的樣式
  abstract setElementStyle(renderElement: any, styleName: string, 
      styleValue: string): void;    
}

須要注意的是在 Angular 4.x+ 版本,咱們使用 Renderer2 替代 Renderer (Angular V2)。

2.Renderer2 API 還有哪些經常使用的方法 ?

export abstract class Renderer2 {
  abstract createElement(name: string, namespace?: string|null): any;
  abstract createComment(value: string): any;
  abstract createText(value: string): any;
  abstract setAttribute(el: any, name: string, value: string,
    namespace?: string|null): void;
  abstract removeAttribute(el: any, name: string, namespace?: string|null): void;
  abstract addClass(el: any, name: string): void;
  abstract removeClass(el: any, name: string): void;
  abstract setStyle(el: any, style: string, value: any, 
    flags?: RendererStyleFlags2): void;
  abstract removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void;
  abstract setProperty(el: any, name: string, value: any): void;
  abstract setValue(node: any, value: string): void;
  abstract listen(
      target: 'window'|'document'|'body'|any, eventName: string,
      callback: (event: any) => boolean | void): () => void;
}

總結

本文主要介紹了 ElementRef 的做用和定義,而後經過一個簡單的示例,展現瞭如何一步步優化已有的功能,但願對初學者能有所啓發。

相關文章
相關標籤/搜索