透過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