Angular - Directive (1/2)

  • 381
  • 0
  • 2018-05-04

透過directive我們可以擴充原有的DOM element、Component甚至其他的directive所沒有的功能。

 

 

大致可分為三種:

1.Components。

2.Structural directives:主要用來改變DOM的配置,EX:ngIf和ngFor,他們可用來移除或加入某些DOM元素,來讓DOM結構產生變化。

3.Attribute directives:改變某個DOM元素、Component甚至是其他directive的顯示方式或行為。

本文主要是探討第三種!

Creative Directive Angular CLI:

ng g d directive\bsbutton (將bsbutton這個directove 建立在 src/app/directive/底下)

 

建立好之後,我們要取得目前套用directive的element,才會知道該如何改變element的內容,

我們可以注入 ElementRef,來取得目前使用directive的element:

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

@Directive({
  selector: '[appBsbutton]'
})
export class BsbuttonDirective {

  //注入 ElementRef,來取得目前使用directive的element
  constructor(private ele: ElementRef) { }

}

所有的directive都必須搭配@Directive這個decorator,以及設定selector參數,

這個selector可以決定要套用的屬性名稱,例如目前是[appBsButton]

那麼在html內使用的時候,<button appBsButton>...</button>或是,<div appBsbutton>...</div>都會套用,

如果在selector 內設定為button[appBsButton],則只有<button appBsButton>...</button>會生效。

 

接著加入ngOnInit(),將目標物件(button)加入class

export class BsbuttonDirective {

  //注入 ElementRef,來取得目前directive所在的element
  constructor(private ele: ElementRef) { }

  ngOnInit() {
    const button = (this.ele.nativeElement as HTMLElement);
    button.classList.add('btn');
    button.classList.add('btn-primary');
  }
}

最後將directive 加入要測試的button 

<button appBsbutton>Button With Attribute Directive</button>

這個按鈕在初始化的時候就會帶入這兩個class (btn btn-primary)

上面的測試是假設我們已經知道傳過來的Element是一個HTMLElement,所以可以透過TypeScript進行轉型,

假設不確定傳過來的東西是什麼,有一個輔助類別:Renderer,可以幫我們設定樣式

Renderer 也是透過注入的方式來使用

修改後的directive.ts

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

@Directive({
  selector: '[appBsbutton]'
})
export class BsbuttonDirective {

  //注入 ElementRef,來取得目前directive所在的element
  constructor(private ele: ElementRef, private renderer: Renderer) { }

  // 很確定 ele 是HTMLElement 的作法
  // ngOnInit() {
  //   const button = (this.ele.nativeElement as HTMLElement);
  //   button.classList.add('btn');
  //   button.classList.add('btn-primary');
  // }

  ngOnInit() {
    //如果不確定ele是不是HTMLElement,就改用renderer來協助設定樣式
    //renderer.setElementClass()的第3個參數,設為true代表加入這個class,設為false則代表移除此class
    this.renderer.setElementClass(this.ele.nativeElement, 'btn', true);
    this.renderer.setElementClass(this.ele.nativeElement, 'btn-primary', true);
  }
}

PS.this.renderer.setElementClass這個method的第三個參數,

設true的時候是設定class,設定false的時候則是移除class

加入@Input,讓Directive更有彈性

在Directive內加入 @Input的部分

照著教學參考網站練習:

在directive.ts 內宣告 @Input() ,

@Input() appBsbutton;

然後改寫ngOnInit()內容

ngOnInit() {
    // 如果不確定ele是不是HTMLElement,就改用renderer來協助設定樣式
    // renderer.setElementClass()的第3個參數,設為true代表加入這個class,設為false則代表移除此class
    this.renderer.setElementClass(this.ele.nativeElement, 'btn', true);
    this.renderer.setElementClass(this.ele.nativeElement, `btn-${this.appBsbutton || 'primary'}`, true);
  }

使用directive的component.html也要調整成

<div class="container">
    <div>
        <h5>
            Demo Directive
        </h5>
    </div>
    <div class="row">
        <div class="col-sm-7 offset-2">
            <button appBsbutton>Button With Attribute Directive</button>
            <button appBsbutton="danger">Danger</button>
            <button appBsbutton="info">Info</button>
        </div>
    </div>
</div>

如果有指定值(給danger or info) ,directive就會直接使用,否則就用預設的primary

執行結果如下:

在剛剛的測試可發現,教學網站內的範例,

Directive內宣告的 Input 名稱跟 Directive 名稱一樣,

這樣的作法,是在HTML button在使用Directive的時候,可以直接想要給的預設值加在Directive後面

<button appBsbutton="danger">Danger</button>

如果我的Input名稱 不想要用跟Directive 名稱相同,

Directive.ts 可以這樣改

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

@Directive({
  selector: '[appBsbutton]'
})
export class BsbuttonDirective {

  //宣告一個跟Directive不同名稱的@Input
  @Input() mouseDownClick;

  //注入 ElementRef,來取得目前使用directive的element
  constructor(private ele: ElementRef, private renderer: Renderer) { }


  ngOnInit() {
    
    this.renderer.setElementClass(this.ele.nativeElement, 'btn', true);
    this.renderer.setElementClass(this.ele.nativeElement, `btn-${this.mouseDownClick || 'primary'}`, true);
  }
}

在HTML內使用的時候則是更改為:

<button appBsbutton mouseDownClick="danger">Danger</button>
<button appBsbutton mouseDownClick="info">Info</button>

給Directive的值就不是接在appBsbutton後面,這樣執行結果是一樣的

PS.之前在練習@Input的時候,在HTML會加上中括號,

加上中括號的話代表綁定的是一個變數,如果沒有加的話代表綁定的是字串

EX:

<app-todo-items [items]="todoItems" iText="DemoInput"></app-todo-items>

todoItems 是一個變數,items 這個@Input() 接收到的是這個變數的資料(有可能是陣列,看todoItems 這個變數怎麼定義)

DemoInput 則是一個字串,傳進去iText的資料是字串 DemoInput