🅰️

[Angular]CSSクラスを動的にセットする方法まとめ

に公開

Angularでは、要素にCSSクラスを動的に適用するために、classバインディングとngClassディレクティブの2つの方法が提供されています。
それぞれの使い方や2つの違いについて、ネット上の情報は古かったり一部しか説明されていなかったりするものが多かったため自分用のメモも兼ねてまとめてみました。

classバインディングの使用方法

classバインディングの3つの使用方法を説明します。
1つの要素に対しこれらを複数組み合わせることも可能です。

1.特定の一つのクラスをboolean変数で制御

<!-- isActiveがtrueの場合にactiveクラスを適用 -->
<div [class.active]="isActive">コンテンツ</div>

ネット上の非公式情報ではこの使い方だけ書かれていることが多いですが、最新のAngular(記事執筆時19)では他の設定方法もあります。

2.クラス名を文字列や配列で指定

<!-- クラス名を文字列で指定 -->
<div [class]="'active disabled'">コンテンツ</div>

<!-- クラス名を配列で指定 -->
<div [class]="['active', 'disabled']">コンテンツ</div>

3.複数のクラスをオブジェクトリテラルで指定

<!-- isActiveがtrueの場合にactiveクラス、isDisabledがtrueの場合にdisabledクラスを適用 -->
<div [class]="{ active: isActive, disabled: isDisabled }">コンテンツ</div>

ngClassディレクティブの使用方法

ngClassディレクティブも同様に、クラスの適用を制御できます。文字列、配列、オブジェクトのいずれかを指定できます。
1つの要素に対して複数のngClassディレクティブを適用することはできません。

<!-- 文字列形式 -->
<div [ngClass]="'active disabled'">コンテンツ</div>

<!-- 配列形式 -->
<div [ngClass]="['active', 'disabled']">コンテンツ</div>

<!-- オブジェクト形式 -->
<div [ngClass]="{ active: isActive, disabled: isDisabled }">コンテンツ</div>

classバインディングとngClassディレクティブの違い

classバインディングがオブジェクトリテラルにも対応しているなら、ngClassとの違いは何があるのか少し混乱してしまったため調べました。

1.変更検出の挙動

classバインディングは、バインドされたオブジェクトの参照が変更された場合にのみビューを更新します。つまり、同じオブジェクトのプロパティだけ変更しても、CSSクラスは更新されません。
一方ngClassディレクティブは、オブジェクトの参照が変わらなくてもプロパティの変更を検出し、ビューを更新します。

サンプルコード

<!-- コンポーネントのテンプレート -->
<div [class]="classObject">classバインディング</div>
<div [ngClass]="classObject">ngClass</div>

<button (click)="changeProperty()">オブジェクトのプロパティのみ変更</button>
<button (click)="changeReference()">オブジェクトの参照を変更</button>
// コンポーネントのクラス
export class AppComponent {
  classObject = {active: true};

  // プロパティのみ変更
  changeProperty() {
    this.classObject.active = !this.classObject.active;
  }

  // オブジェクトの参照を変更
  changeReference() {
    this.classObject = {
      active: !this.classObject.active
    };
  }
}

上記の例で、chagePropertyメソッドを呼び出してclassObject.activeを切り替えても、classバインディングを使用している要素のクラスは更新されません。
一方、ngClassディレクティブを使用している要素のクラスは更新されます。

2.スペース区切りでの複数クラスの適用

classバインディングでは、オブジェクトのキーとしてスペース区切りのクラス名を指定しても、正しく適用されません。
一方ngClassディレクティブでは、キーにスペース区切りのクラス名を指定することで、複数のクラスをまとめて適用できます。

サンプルコード

<!-- classバインディングの場合 -->
<div [class]="{ 'round blue': true }">classバインディング</div>
<!-- 適用されるクラス: なし -->

<!-- ngClassディレクティブの場合 -->
<div [ngClass]="{ 'round blue': true }">ngClass</div>
<!-- 適用されるクラス: round, blue -->

上記の例では、classバインディングではスペース区切りのクラス名をキーとして指定しても適用されませんが、ngClassディレクティブでは正しく適用されます。

「1.変更検出の挙動」と「2.スペース区切りでの複数クラスの適用」についてサンプルプロジェクトで視覚化してみました。
「変更検出の挙動」では「オブジェクトの参照を変更」と「オブジェクトのプロパティのみ変更」についてボタンで動作確認が可能です。
「スペース区切りの挙動」では、ngClassディレクティブの方にのみclassが適用されていることがわかります。

どちらを使えばいいのか?

Angularとしては、シンプルなユースケースではclassバインディングの使用を推奨しているようです。
ngClassディレクティブは、より複雑なクラス適用のシナリオで使用することが適しているとのこと。
この辺りの議論からその雰囲気が伺えます。

https://github.com/angular/angular/issues/40623

https://github.com/angular/angular/pull/59240

自分自身、今までclassバインディングはboolean変数による単独のクラス付け替えに使うものという認識で、複数クラスを動的に付け替える時は毎回ngClassを使ってしまっていました。
そういった用途でもclassバインディングで十分ですし、ngClassディレクティブと違いimportも不要なため今後はclassバインディングを第一選択肢として認識を改めようと思いました。

参考

Discussion