Dev Study
← 解説「属性ディレクティブ — ngClass と ngStyle」に戻る

サンプルコードで身につける: 属性ディレクティブ — ngClass と ngStyle

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1ngClass にオブジェクトを渡す

ngClass の基本形で、「クラス名: 付ける条件」のオブジェクトを渡します。条件が true のクラスだけが要素に付くので、warn と error のように排他的な状態の見た目を1か所で切り替えられます。imports には CommonModule 一括ではなく、使うものだけを NgClass のように個別に登録するのが現在の推奨です。

import { Component } from '@angular/core';
import { NgClass } from '@angular/common';

@Component({
  selector: 'app-alert',
  // CommonModule 一括ではなく NgClass を個別に imports する
  imports: [NgClass],
  // 条件が true のクラスだけが付く
  template:
    '<p [ngClass]="{ warn: level === \'warn\', error: level === \'error\' }">' +
    '  ディスク使用率が高くなっています' +
    '</p>',
  styles: ['.warn { color: orange; } .error { color: red; font-weight: bold; }'],
})
export class AlertComponent {
  level = 'warn'; // 'error' に変えると付くクラスも変わる
}

2ngClass に配列や文字列を渡す

ngClass はオブジェクトのほかに、クラス名の配列やスペース区切りの文字列も受け取れます。「常に付く基本クラス + 種類ごとの装飾クラス」のような組み合わせを、クラス側で1つの値にまとめて渡せるのが便利な点です。付け外しの条件が不要で、クラスの組み合わせだけを差し替えたいときはこの形が簡潔です。

import { Component } from '@angular/core';
import { NgClass } from '@angular/common';

@Component({
  selector: 'app-chip',
  imports: [NgClass],
  // 配列でも 'chip chip-primary' という文字列でも同じ結果になる
  template: '<span [ngClass]="chipClasses">おすすめ</span>',
  styles: [
    '.chip { padding: 4px 8px; border-radius: 9999px; }' +
    ' .chip-primary { background: teal; color: white; }',
  ],
})
export class ChipComponent {
  // 基本クラス + 種類ごとのクラスをクラス側で組み立てる
  chipClasses = ['chip', 'chip-primary'];
}

3ngStyle で値に応じたスタイルを当てる

幅や色のように「値そのものがデータから決まる」スタイルは、クラスの付け外しでは表現しきれないので ngStyle でまとめて渡します。進捗バーの幅をパーセントの値から作るのは、その代表的な実務例です。固定の見た目は styles のCSSに置き、値で変わる部分だけを ngStyle に絞るのが読みやすい分担です。

import { Component, signal } from '@angular/core';
import { NgStyle } from '@angular/common';

@Component({
  selector: 'app-progress-bar',
  imports: [NgStyle],
  // 値によって変わるスタイルだけを ngStyle で渡す
  template:
    '<div class="bar"' +
    '     [ngStyle]="{ width: percent() + \'%\', background: barColor() }">' +
    '</div>',
  // 固定の見た目は通常のCSSに置く
  styles: ['.bar { height: 8px; border-radius: 4px; }'],
})
export class ProgressBarComponent {
  percent = signal(72);
  barColor = signal('teal');
}

4[class.xxx] と ngClass の使い分け

切り替えるクラスが1つだけなら、以前学んだ [class.xxx] の単発バインドが最も簡潔で、imports の追加も不要です。複数のクラスを同時に切り替えるなら ngClass にまとめる、という使い分けを1つのテンプレートで対比した例です。「1つなら class バインド、複数なら ngClass」という目安を持っておくと、実務でも迷わず書けます。

import { Component, signal } from '@angular/core';
import { NgClass } from '@angular/common';

@Component({
  selector: 'app-menu-item',
  imports: [NgClass],
  template:
    // クラスが1つだけなら [class.xxx] が簡潔(imports も不要)
    '<li [class.selected]="isSelected()">通知設定</li>' +
    // 複数のクラスを同時に切り替えるなら ngClass にまとめる
    '<li [ngClass]="{ selected: isSelected(), disabled: isDisabled() }">' +
    '  プラン変更' +
    '</li>',
  styles: ['.selected { font-weight: bold; } .disabled { opacity: 0.5; }'],
})
export class MenuItemComponent {
  isSelected = signal(true);
  isDisabled = signal(false);
}

5状態から computed でクラスを組み立てる実務例

注文ステータスのような状態に応じてバッジの見た目を変える、実務でよくある形です。どのクラスを付けるかの判定を computed に切り出し、テンプレートには [ngClass]="badgeClass()" と結果だけを渡します。条件がテンプレートに散らばらないので、ステータスが増えたときも computed の1か所を直すだけで済みます。

import { Component, computed, signal } from '@angular/core';
import { NgClass } from '@angular/common';

type Status = 'pending' | 'shipped' | 'canceled';

@Component({
  selector: 'app-order-status',
  imports: [NgClass],
  // 判定は computed に寄せ、テンプレートには結果だけを渡す
  template: '<span class="badge" [ngClass]="badgeClass()">{{ status() }}</span>',
  styles: [
    '.badge { padding: 2px 8px; } .pending { background: gold; }' +
    ' .shipped { background: teal; color: white; }' +
    ' .canceled { background: gray; color: white; }',
  ],
})
export class OrderStatusComponent {
  status = signal<Status>('shipped');

  badgeClass = computed(() => ({
    pending: this.status() === 'pending',
    shipped: this.status() === 'shipped',
    canceled: this.status() === 'canceled',
  }));
}