Angularのディレクティブを極める!ngIf・ngFor・ngSwitchなど。

Angularのディレクティブを極める!ngIf・ngFor・ngSwitchなど。

この記事では、Angularのテンプレートを使いこなすためには避けて通れないディレクティブについて説明していきたい。

「ディレクティブ」という言葉は聞いたことがないという人もいるかもしれないが、AngularのHTML上で使用するngIfやngForの事。これらの機能はAngularの開発者であれば一度は使ったことがあるだろう。

この記事ではngIfやngForといった使用頻度の高いディレクティブについて特に重点をおいて実装の際に気を付ける点など説明していきたいと思う。またカスタムディレクティブの作成方法についても記しておくので最後まで目を通してほしい。

3種類のディレクティブ

Angularのディレクティブは以下の3種類に分類される。

  • コンポーネント
  • 構造ディレクティブ
    ⇒動的なコンテンツをHTML上で定義するために必要な機能。
    ngIf・ngSwitch・ngFor・ngTemplate・ngTemplate
  • 属性ディレクティブ
    ⇒HTMLの要素に結びついた機能。
    [ngClass][ngStyle][(ngModel)]

またカスタムディレクティブとして自分たちで新しいディレクティブを作成することもできる。

構造ディレクティブ

まずはngIfngForなどの構造ディレクティブについて説明していきたい。

構造ディレクティブはHTMLに動的に生成・変化させるために使われる。
HTMLファイル内では、構造ディレクティブはアスタリスク(*マーク)を使って表す。

ngIf 要素の表示・非表示

ngIfは条件式の結果がTrueであればその要素を表示するというシンプルなディレクティブだ。

以下はngIfを使用したシンプルな例。ボタンをクリックするたびにテキストの表示・非表示が切り替わるはずだ。

public loggedIn = false;

changeLogInStatus() {
  this.loggedIn = !this.loggedIn;
}
<label *ngIf="loggedIn">あなたはログインしています。</label>
<button (click)="changeLogInStatus()">ログイン/ログアウト</button>

showTextの値(trueとfalse)が変化すると即座にビューに反映されたのが分かるはずだ。

ngIfによって非表示にされている要素は「ただ見えない」という訳ではなくHTML上からそのものが破棄されている。
よってngIfで非表示になている要素はidなどで検索しても見つからない。

else 条件を満たさない場合、他の要素を表示する

elseはngIfに追記する形で使用する。

elseを指定することにより、ngIf式が条件を満たさなかった場合に表示するテンプレートを指定することができる。

<label *ngIf="loggedIn; else notLoggedInText">あなたはログインしています。</label>
<button (click)="changeLogInStatus()">ログイン/ログアウト</button>

<ng-template #notLoggedInText>
  <label>ログインしていません。</label>
</ng-template>

then else 条件を満たす場合のテンプレートも指定する

条件を満たす場合もng-Templateを使用してテンプレートを指定したい場合もあるだろう。
そういった場合は、then else構文を使用する。

<div *ngIf="loggedIn; then loggedInText; else notLoggedInText"></div>
<button (click)="changeLogInStatus()">ログイン/ログアウト</button>

<ng-template #loggedInText>
  <label>あなたはログインしています。</label>
</ng-template>

<ng-template #notLoggedInText>
  <label>ログインしていません。</label>
</ng-template>

ngSwitch

ngSwitchは式の値によって表示するコンテンツを切り替えるディレクティブ。
Swicth構文はどのプログラミング言語にもあり、それらと同じような感覚で使えば問題ないはずだ。

下の例ではドロップダウンで選択したアイテムによって表示するテキストの内容を切り替えている。

<form>
  <label>支払い方法</label>
    <select name="payment" [(ngModel)]="paymentMethod">
    <option value="card">クレジットカード</option>
    <option value="bank">銀行振込</option>
    <option value="convenienceStore">コンビニ振込</option>
  </select>

  <div [ngSwitch]="paymentMethod">
    <span *ngSwitchCase="'card'">クレジットカードの暗証番号を入力してください。</span>
    <span *ngSwitchCase="'bank'">料金は銀行で振り込んでください。</span>
    <span *ngSwitchCase="'convenienceStore'">料金はお近くのコンビニエンスストアでお支払いください。</span>
    <span *ngSwitchDefault>支払い方法を選択してください。</span>
  </div>
</form>

ngSwitchはngIfで代用することもできるが、選択肢の数が多い場合や条件がEnumで指定されている場合などは、ngSwitchを使用したほうが読みやすいだろう。

ngFor

ngForはHTML内で配列やマップなどといったオブジェクトを出力するためのディレクティブ。

ここではよく使われるテーブル要素を使って簡単な実装例を紹介しよう。

まずは出力するためのデータを用意する。ここではPersonオブジェクトを作成した。

interface Person {
  personId: number;
  firstName: string;
  lastName: string;
  age: number;
}

public persons: Array<Person> = [
  {
    personId: 0,
    firstName: '和子',
    lastName: '田野',
    age: 25
  },{
    personId: 1,
    firstName: '康夫',
    lastName: '原',
    age:42
  },{
    personId: 2,
    firstName: '博之',
    lastName: '森川',
    age: 36
  }
];

HTMLファイル内にtable要素を作成する。
ngForで出力されたオブジェクトの内容を各行に出力する。

<table class="table">
  <tr>
    <th>名前</th>
    <th>年齢</th>
  </tr>

  <tr *ngFor="let person of persons">
    <td>{{ person.lastName + ' ' + person.firstName }}</td>
    <td>{{ person.age + '歳' }}</td>
  </tr>
</table>

テーブルの見た目を整えるためにCSSを追加する。


table, th , td  {
  border: 0.5px solid gray;
  border-collapse: collapse;
  padding: 10px;
}
table tr:nth-child(odd) {
  background-color: #f1f1f1;
}
table tr:nth-child(even) {
  background-color: #ffffff;
}

属性ディレクティブ

次に属性ディレクティブと呼ばれるタイプのディレクティブを見ていこう。
属性ディレクティブに属するのはngStyle・ngClass・ngModelの3つだ。

ngModelは双方向バインディングを使用するために使われるディレクティブで、バインディングの記事で詳しく説明しているのでこちらの記事を読んでほしい。

ngClass

まずHTML要素のクラスを切り替えるためのディレクティブ「ngClass」から見ていこう。
ngClassは[class.my-class]のようなクラスバインディングとよく似た役割を持ち、双方は置き換え可能である場合が多い。

1つ・2つのクラスを切り替える

ngClassの記述方法にいくつかのバリエーションがある。
ここではまず1つのクラスのオン・オフを切り替えるパターンを見てみよう。

public isWarning = false;
.warning {
  color: yellow;
}
<label [ngClass]="{'warning' : isWarning}">これはサンプルのテキストです。</label>
<input type="checkbox" [(ngModel)]="isWarning" />

インターネットブラウザのデベロッパーツールで確認するとclassが追加・削除されているのが分かる。

次にngClassを使って2つのクラスを切り替えるパターン。
この場合は三項演算子を使って表記する。この記述の場合{}鍵カッコは付かないので気を付ける。

.normal {
  color: green;
}
<label [ngClass]="isWarning ? 'warning' : 'normal'">これはサンプルのテキストです。</label>

3つ以上のクラスを切り替える

次にngClassを使って3つ以上のクラスを切り替える例を見てみよう。
ここでは実践でもよく使われるenumを使ったパターンを紹介する。

enum TextType {
  NORMAL = 'normal', WARNING = 'warning', DANGER = 'danger'
}

TextType = TextType;
public textType: TextType = TextType.NORMAL;
.normal {
  color: green;
}

.warning {
  color: yellow;
}

.danger {
  color: red;
  font-weight: bold;
}
<label [ngClass]="{'normal': textType === TextType.NORMAL,
                  'warning': textType === TextType.WARNING,
                  'danger': textType === TextType.DANGER
                  }">これはサンプルのテキストです。</label>


<select [(ngModel)]="textType">
  <option value="normal">通常</option>
  <option value="warning">注意</option>
  <option value="danger">危険</option>
</select>

上記のようなパターンは鍵カッコを使用して次のように記述することができる。

<label [ngClass]="{'normal': TextType.NORMAL,
                  'warning': TextType.WARNING,
                  'danger': TextType.DANGER
                  }[textType]">これはサンプルのテキストです。</label>

ngStyle

次にngStyleを見ていく。

ngStyleはngClassと似た構文を持つが切り替える対象がクラスではなくCSSスタイルとなる。
ngStyleは[style.color]のようなスタイルバインディングとよく似た役割を持ち、双方は置き換え可能である場合が多い。

ひとつのプロパティを変更する

ngStyleを使ってテキストの色を変更するパターンを紹介する。

public isWarning = false;
<label [ngStyle]="{ color: isWarning ? 'red' : 'black' }">これはサンプルのテキストです。</label>
<input type="checkbox" [(ngModel)]="isWarning" />

チェックボックスをオン・オフするとテキストの色が変化するはずだ。

ngStyleはすべてのCSS要素に対して使用できる。

displayプロパティに使用すれば要素の表示・非表示を切り替えることもできる。

[ngStyle]="{'display' : isVisible ? 'none' : 'unset' }"

font-size.pxのように記述すれば値の単位を指定することもできる。

[ngStyle]="{'font-size.px' : isWarning ? 24 : 12 }"

複数のプロパティを設定する

複数のプロパティをオブジェクトとしてまとめてngStyleに引き渡すこともできる。

public textStyles = {
  background: 'blue',
  color: 'red',
  fontSize: '24px'
};
[ngStyle]="textStyles"

ただしこのパターンはビュー・ロジック・スタイルの分別が曖昧になり、望ましくないのでngClassを使用したほうが無難だろう。