はじめに
Angular技術要素解説シリーズ、第3弾です。
今回は、コンポーネントの見た目を定義するテンプレートの中で使う構文を、体系的に整理します。
データバインディングの4種類と、Angular 17で刷新された制御フロー構文(@if・@for・@switch・@defer)が中心のテーマです。
この記事で扱う技術要素
- Interpolation(文字列補間)
- Property Binding(プロパティバインディング)
- Event Binding(イベントバインディング)
- Two-way Binding(双方向バインディング)
- Control Flow構文(
@if/@for/@switch) - 旧構造ディレクティブ(
*ngIf/*ngFor) -
@defer(遅延読み込みブロック)
データバインディングとは
データバインディングとは、TypeScript側のデータとHTML(テンプレート)側の表示を結びつける仕組みです。
Angularには、データの流れる方向によって4種類のバインディングがあります。
1. Interpolation(文字列補間):TypeScript → HTML
{{ }}(二重中括弧)を使い、TypeScript側の値をテキストとして埋め込みます。
@Component({
selector: 'app-profile',
standalone: true,
template: `
<p>名前: {{ userName }}</p>
<p>年齢: {{ age }}歳</p>
<p>誕生年: {{ 2026 - age }}</p> <!-- 簡単な式も書ける -->
`,
})
export class ProfileComponent {
userName = 'はなこ';
age = 30;
}
表示結果:
名前: はなこ
年齢: 30歳
誕生年: 1996
2. Property Binding(プロパティバインディング):TypeScript → HTML
[属性名]="式"の形式で、DOM要素のプロパティ(単なる文字列ではなく、真偽値・オブジェクトなども含む)にTypeScript側の値を反映します。
@Component({
selector: 'app-image-card',
standalone: true,
template: `
<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isLoading">送信</button>
`,
})
export class ImageCardComponent {
imageUrl = '/assets/photo.jpg';
imageAlt = 'サンプル画像';
isLoading = true; // trueの間、ボタンが無効化される
}
src="imageUrl"のように[]を付けずに書いてしまうと、imageUrlという文字列そのものが属性値として使われてしまいます。
「TypeScript側の変数の値を反映したい」場合は[]が必須です。
3. Event Binding(イベントバインディング):HTML → TypeScript
(イベント名)="処理()"の形式で、クリックなどのDOMイベントが発生したときにTypeScript側のメソッドを呼び出します。
@Component({
selector: 'app-like-button',
standalone: true,
template: `
<button (click)="onLikeClick()">いいね!({{ likeCount }})</button>
`,
})
export class LikeButtonComponent {
likeCount = 0;
onLikeClick() {
this.likeCount++;
}
}
$eventという特別な変数を使うと、発生したイベントオブジェクト自体を受け取ることもできます。
@Component({
selector: 'app-search-box',
standalone: true,
template: `
<input (input)="onInput($event)" placeholder="検索ワードを入力">
<p>入力中: {{ currentText }}</p>
`,
})
export class SearchBoxComponent {
currentText = '';
onInput(event: Event) {
const target = event.target as HTMLInputElement;
this.currentText = target.value;
}
}
4. Two-way Binding(双方向バインディング):HTML ⇄ TypeScript
[( )](通称「バナナ・イン・ボックス」)を使うと、Property BindingとEvent Bindingを同時に行えます。
フォームの入力欄などで頻繁に使われます。
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms'; // ngModelを使うために必要
@Component({
selector: 'app-name-input',
standalone: true,
imports: [FormsModule],
template: `
<input [(ngModel)]="userName" placeholder="お名前">
<p>こんにちは、{{ userName }}さん</p>
`,
})
export class NameInputComponent {
userName = '';
}
[(ngModel)]="userName"は、実際には以下のコードの糖衣構文(シンタックスシュガー)です。
<!-- [(ngModel)]="userName" は、以下と同じ意味 -->
<input [ngModel]="userName" (ngModelChange)="userName = $event">
FormsModuleが提供するngModelと[( )]を組み合わせることで、「入力欄に文字を打つとuserNameが更新され、userNameが変わると入力欄の表示も更新される」という双方向の同期が実現します。
データバインディング早見表
| 記法 | 名称 | データの向き | 用途例 |
|---|---|---|---|
{{ value }} |
Interpolation | TypeScript → HTML | テキストの表示 |
[prop]="value" |
Property Binding | TypeScript → HTML | 属性・プロパティへの値の反映 |
(event)="handler()" |
Event Binding | HTML → TypeScript | クリック等のイベント処理 |
[(ngModel)]="value" |
Two-way Binding | HTML ⇄ TypeScript | フォーム入力の同期 |
制御フロー構文(Control Flow)
Angular 17で、テンプレート内の条件分岐・繰り返し処理を書くための新しい構文が導入されました。
従来の*ngIf・*ngForと比べて高速で、可読性も高いとされています。
@if:条件分岐
@Component({
selector: 'app-status',
standalone: true,
template: `
@if (isLoggedIn) {
<p>ようこそ、{{ userName }}さん</p>
} @else if (isLoading) {
<p>読み込み中...</p>
} @else {
<p>ログインしてください</p>
}
`,
})
export class StatusComponent {
isLoggedIn = false;
isLoading = true;
userName = 'はなこ';
}
@for:繰り返し処理
@Component({
selector: 'app-todo-list',
standalone: true,
template: `
<ul>
@for (todo of todos; track todo.id) {
<li>{{ todo.title }} - {{ todo.done ? '完了' : '未完了' }}</li>
} @empty {
<li>タスクはありません</li>
}
</ul>
`,
})
export class TodoListComponent {
todos = [
{ id: 1, title: '記事を書く', done: false },
{ id: 2, title: 'コードレビュー', done: true },
];
}
track todo.idの部分は必須です。
これは、リストの各要素を一意に識別するためのキーで、リストの内容が変化した際に「どの要素が追加・削除・並び替えされたか」をAngularが効率よく判断するために使われます(Reactのkeypropと同じ役割です)。
@emptyブロックは、繰り返し対象の配列が空の場合にだけ表示される特別なブロックです。
@switch:多分岐
@Component({
selector: 'app-badge',
standalone: true,
template: `
@switch (userRole) {
@case ('admin') {
<span class="badge admin">管理者</span>
}
@case ('editor') {
<span class="badge editor">編集者</span>
}
@default {
<span class="badge">一般ユーザー</span>
}
}
`,
})
export class BadgeComponent {
userRole: 'admin' | 'editor' | 'viewer' = 'editor';
}
旧構造ディレクティブ(*ngIf / *ngFor)との違い
Control Flow構文が登場する以前は、以下のような*(アスタリスク)付きの構造ディレクティブが使われていました。
既存プロジェクトのコードや古い解説記事では、まだこちらの書き方も頻繁に登場します。
<!-- 旧構文: *ngIf -->
<p *ngIf="isLoggedIn">ようこそ!</p>
<!-- 旧構文: *ngFor -->
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodoId">{{ todo.title }}</li>
</ul>
| 観点 | 旧構文(*ngIf/*ngFor) |
新構文(@if/@for) |
|---|---|---|
| 追加の import |
CommonModuleのimportが必要 |
不要(テンプレートに組み込みの構文) |
| パフォーマンス | 相対的に低速 | 最適化されており高速 |
| track/trackBy | 別メソッドとして定義(trackBy) |
インラインで直接指定(track) |
| 現在の位置づけ | 既存コードで現役、非推奨ではないが新規では非推奨 | Angular 17以降の推奨構文 |
新しくコードを書く場合は@if・@for・@switchを使い、既存コードの*ngIf・*ngForを読む際は「意味は同じ、書き方が違うだけ」と理解しておけば困りません。
@defer:遅延読み込みブロック
@deferは、テンプレートの一部の読み込み・表示を、必要になるまで遅延させる構文です。
初期表示速度を改善したい場合に使います。
@Component({
selector: 'app-product-page',
standalone: true,
imports: [ReviewSectionComponent],
template: `
<h1>{{ productName }}</h1>
<p>{{ productDescription }}</p>
@defer (on viewport) {
<!-- この部分は、画面にスクロールして表示されるまで読み込まれない -->
<app-review-section [productId]="productId" />
} @placeholder {
<p>レビュー欄を準備中です...</p>
} @loading (minimum 500ms) {
<p>読み込み中...</p>
}
`,
})
export class ProductPageComponent {
productName = 'ワイヤレスイヤホン';
productDescription = '高音質・ノイズキャンセリング対応';
productId = 123;
}
on viewportは「要素が画面内に入ったら読み込みを開始する」というトリガーです。
他にもon idle(ブラウザが暇な時に読み込む)、on interaction(クリック等の操作をきっかけに読み込む)、on timer(5s)(指定時間後に読み込む)などのトリガーが用意されています。
ページの初期表示に不要な、レビュー欄や関連商品一覧のようなセクションを@deferで囲むことで、最初に読み込むJavaScriptの量を減らし、初期表示速度(特にInteraction to Next Paintの指標)を改善することができます。
まとめ
| 構文 | 役割 |
|---|---|
{{ }} |
TypeScriptの値をテキストとして表示する |
[prop] |
TypeScriptの値をプロパティに反映する |
(event) |
DOMイベントの発生時にTypeScript側の処理を呼ぶ |
[( )] |
プロパティとイベントを組み合わせた双方向の同期 |
@if / @for / @switch
|
Angular 17以降の、高速な条件分岐・繰り返し構文 |
*ngIf / *ngFor
|
旧来の構造ディレクティブ(既存コードで現役) |
@defer |
テンプレートの一部読み込みを遅延させ、初期表示を高速化する構文 |
次回は、Angularアプリの機能を裏側で支える依存性注入(DI)とサービスについて解説します。
学習に使った書籍
本記事の内容整理にあたっても、以下の書籍を参考にしました。
- Angular 超入門 第2版 [ 掌田津耶乃 ]
- Angularアプリケーションプログラミング 開発の勘所と手順をしっかりマスター [ 山田祥寛 ]
- Angular Webアプリ開発スタートブック [ 大澤文孝 ]
※上記リンクはアフィリエイトリンクを含みます。