0%

Angular-class and style binding

起源

在各家框架中,使用程式來增減目標元素的 class 和 style 的內容,這是非常常用的技巧,在 Angular 也不例外,所以,這一篇特此紀錄一些 Angular 增減目標元素 class 和 style 的方法。

本文目錄

這系列的文章會記錄以下的內容

  1. 如何動態 binding 目標元素的 class
  2. 如何動態 binding 目標元素的 style
  3. 在 Angular 中判定目標元素應該以哪一個 style 為準的優先序

學習點

首先,先記錄為目標元素動態增減 class 的方法。

綁定單一個 css class

直接上一段程式碼
<div [class.sale]="onSale">...</div>
上面這一段程式碼的效果為,
當 onSale 的值為 true 的時候,會為這個 div 元素的 class 加入 sale 這個 className,
反之,當 onSale 為 false 則會把 class 裡的 sale 刪掉。

綁定多個 css class

直接上一段程式碼
<div [class]="classExpression"></div>
這個 classExpression 可以是以下幾種格式的其中一種:

  1. 一段字串,並將想要加入的 class 之間以空白格隔開
    範例程式碼
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    --- app.component.ts ---
    @Component({
    selector: 'my-app',
    template: `
    <div [class]="extraClass">...</div>
    `
    })
    export class AppComponent {
    extraClass = 'modal success'; // 這是一段字串,將想加入的 class Name 之間以空白格隔開
    }

最終結果會長這樣
multi-class-binding-picture

  1. 傳入一個物件,其成員屬性為想加入的 class - 注意傳址的問題
    這個要傳入的物件所含有的成員屬性名稱,就是要被加入到目標元素的 class 內的 class 名稱,
    而這些成員屬性會接 truthy 或 falsy 的值,已決定是否該 className 會被加入到目標元素的 class 中。

範例程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--- app.component.ts ---
@Component({
selector: 'my-app',
template: `
<p [class]="extraClass">Test Class object</p>
<button type="button" (click)="changeClass()">Change</button>
`
})
export class AppComponent {
onSale = true;
extraClass = {
modal: true,
success: false
};

changeClass(): void {
this.extraClass.success = !this.extraClass.success;
}
}

上面這個範例會出現錯誤!!!
你會發現,當你按下按鈕之後,p 段落的 success 這個 className 並沒有隨著你按下按鈕,而被增減。

這是因為你是傳入物件進去,當你按下按鈕,只是單純地去修改該物件裡的屬性值,該物件的位址並沒有不同,所以, Angular 是不會偵測到裡面的值有更動過的,所以,才會造成以上的問題。

所以,要修改一下上面的範例程式碼內容

1
2
3
4
changeClass(): void {
this.extraClass.success = !this.extraClass.success;
this.extraClass = { ...this.extraClass };
}

我們重新創一個物件,並設給 extraClass,如此一來,就可以成功達成增減 success 這個 class 囉。

  1. 傳入一個陣列
    這個方法就是傳入一個陣列,裡面的元素都是字串,它們的值就是要加入目標元素的 class。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Component({
    selector: 'app-root',
    template: `
    <p [class]="extraClass>Test Class</p>
    <button type="button" (click)="spliceArr()>Change</button>
    `
    })
    export class AppComponent {
    addClassArr = ['modal', 'success'];

    spliceArr(): void {
    this.addClassArr = this.addClassArr.slice(1);
    }
    }

    要注意的是,如果,你想要刪減目標元素的 class 的話,
    一樣是要將增減後的全新的陣列設給原本的陣列,
    不然,又會出現傳址機制,導致沒有被偵測到有改變的問題。

  2. ternary operators (三元運算式)
    可以加入 三元運算式 來決定是哪一種 class

    1
    <div [class]="activated ? 'success' : 'error'">ternary operators</div>

    當 activated 這個變數為 true 時,為該元素的 class 加入 success,反之,加入 error。

Appendix

Q1. [ngClass][class] 的差異? - 會把 host element 相同 className 刪掉的差異?
Ans:
在 Angular 裡面,我們也會用 ngClass 來做到動態增減目標元素的 class 內容的效果。
但它和 [class] 方法差在,[class] 會在一開始就先把相同的 className 拔掉。
什麼意思呢? 看一下下面的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--- child.component.ts ---
@Component({
selector: 'app-child',
template: `<div>Child Template</div>`
styles: [`
:host(.modal) {
color: red;
}
`]
})
export class ChildComponent {
@HostBinding('class.modal') modal = truel
}

--- app.component.ts ---
<app-child [class.modal]="false"></app-child>

上面這段程式碼,可以看到 [class.modal]="false" 是代表不為目標元素加入 modal 這個 class。
另外,範例中的 app-child 會因為 @HostBinding('class.modal') modal = true ,而有一個既有的 modal 在它的 class 中了,
但它會因為 [class.modal]="false" 的效果,而導致一開始存在在 host element 中的 modal 這個 class 被刪掉。
這就是上面所說的 [class] 的寫法,會在一開始就先把既有的相同 className 的內容移除的的意思。

那來看一下, [ngClass] 差在哪

1
2
3
4
5
--- app.component.ts ---
isActive = false;

--- app.component.html ---
<app-child [ngClass]="{ 'modal': isActive }"></app-child>

改寫成用 ngClass 的方式,就算它這邊是判定不要加入 modal 這個 class,但是,它也不會一開始就先把 app-child 的 host element 的 class 中,既有的 modal 給刪掉。

但是,後續如果你操縱 isActive 這個變數,當它先從原始值的 false 換成 true,再換成 false 的時候,這個時候,在 host element 裡的 modal class 還是會被移除掉喔。

所以,可以看到 [ngClass][class] 的差異是在一開始是否會為指定元素的 class 內,相同的 className 移除與否的差別。

那為什麼要知道這個東西的差別,有時候,我們可能會引入外部元件或第三方套件具有一些很常用的 class,那我們就很有可能使用 [class] 綁定在該指定元素上,而造成 class 被移除掉,進而造成該元件原本設計的 css style 不見了的問題。

解決的辦法的話,除了小心命名 className 之外,或者就是直接使用 ngClass 來避免這種狀況囉。

Conclusion

上面記錄了四種 Angular 可以用來變換指定元素地 class 內容的方法,
要特別注意的是,如果,是傳入物件或陣列的話,會有傳址的問題需要特別處理一下。

Reference

  1. Angular Official Introduction of class binding