0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Angular技術要素解説】テンプレート構文完全ガイド:データバインディングと制御フロー構文

0
Posted at

はじめに

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)とサービスについて解説します。

学習に使った書籍

本記事の内容整理にあたっても、以下の書籍を参考にしました。

※上記リンクはアフィリエイトリンクを含みます。

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?