Dev Study
← コース一覧

Angular

Googleが開発するフルスタックなフロントエンドフレームワークを学びます。スタンドアロンコンポーネントとシグナルを中心に、モダンなAngularの書き方を身につけます。最新のv22で正式版になったシグナルフォームやゾーンレスにも触れます。

公式ドキュメント: Angular 日本語ドキュメント

コンポーネント基礎

  1. 1
    コンポーネント — UIの基本単位
    Angularの画面は、コンポーネントという部品を組み合わせて作ります。コンポーネントはTypeScriptのクラスに @Component デコレータを付けたもので、selector(HTMLタグ名)と template(表示するHTML)を指定します。selectorに app-hello と書けば、ほかの場所から <app-hello /> というタグで呼び出せます。
  2. 2
    テンプレート補間 — {{ }} で値を表示する
    クラスが持つ値をテンプレートに表示するには、{{ userName }} のような二重波カッコを使います。これをテンプレート補間と呼び、カッコの中にはプロパティ名のほか、{{ price * 1.1 }} のような簡単な式も書けます。式の結果は文字列に変換されてHTMLに埋め込まれます。
  3. 3
    プロパティバインディング — [ ] で値を流し込む
    HTML要素の属性にクラスの値を渡すには、属性名を角カッコで囲んで [src]="imageUrl" のように書きます。これをプロパティバインディングと呼び、右辺の式が評価されて、その結果が要素のプロパティに設定されます。クラス側の値が変われば、要素の状態も自動で更新されます。
  4. 4
    イベントバインディング — ( ) で操作を受け取る
    クリックや入力などのユーザー操作をクラスのメソッドにつなぐには、イベント名を丸カッコで囲んで (click)="increment()" のように書きます。これをイベントバインディングと呼び、イベントが発生するとカッコ内の式が実行されます。$event と書けば、イベントの詳細情報をメソッドに渡すこともできます。
  5. 5
    クラスとスタイルのバインディング
    CSSクラスの付け外しを条件で切り替えるには [class.クラス名]="条件" と書きます。条件が true のときだけそのクラスが付与されます。同じように [style.スタイル名]="値" で、個別のスタイルに値を直接バインドすることもできます。
  6. 6
    テンプレートとスタイルの外部ファイル化
    これまでのレッスンでは template にHTMLを文字列で直接書いてきましたが、実務ではHTMLとCSSをそれぞれ別ファイルに分けるのが一般的です。@Component の template を templateUrl: './xxx.component.html' に、styles を styleUrl: './xxx.component.css' に置き換えると、見た目はファイルに、ロジックはクラスに、と役割ごとにファイルが分かれます。
  7. 7
    ng-content — 中身を差し込めるコンポーネント
    カードや枠線付きパネルのように「外側の見た目は共通だが、中身は使う側で決めたい」部品を作るには <ng-content /> を使います。子のテンプレートに <ng-content /> を置くと、親が <app-card>ここの中身</app-card> のようにタグで囲んだHTMLが、その場所に差し込まれて表示されます。この仕組みをコンテンツ投影と呼びます。

制御フロー

  1. 8
    @if — 条件で表示を切り替える
    テンプレート内で条件分岐をするには @if を使います。@if (条件) { ... } と書くと、条件が true のときだけ中身が描画されます。@else { ... } や @else if (条件) { ... } を続けて、条件ごとの出し分けもできます。
  2. 9
    @for — 配列を繰り返し表示する
    配列の要素を順に描画するには @for を使います。@for (item of items; track item.id) { ... } と書くと、配列の要素ごとに中身が繰り返し描画されます。track には各要素を一意に識別できる値(IDなど)を必ず指定します。@empty { ... } を続ければ、配列が空のときの表示も書けます。
  3. 10
    @switch — 値ごとに表示を分ける
    1つの値が取りうる複数のパターンで表示を分けるには @switch を使います。@switch (値) { @case (候補) { ... } } と書くと、値に一致した @case の中身だけが描画されます。どの @case にも一致しなかったときの表示は @default { ... } に書きます。
  4. 11
    @defer — 重い部分を後から読み込む
    @defer は、テンプレートの一部の読み込みを必要になるまで遅らせる仕組みです。@defer { <app-heavy-chart /> } と囲むと、その中のコンポーネントのコードは最初のページ表示には含まれず、あとから別途読み込まれます。最初に表示されるJavaScriptが小さくなるため、ページの初期表示が速くなります。

シグナルと状態

  1. 12
    signal — リアクティブな状態の入れ物
    シグナルは「値が変わったことを周りに知らせる入れ物」です。count = signal(0) のように作り、count() と関数のように呼んで値を読み、count.set(5) で更新します。現在値をもとに更新するなら count.update((v) => v + 1) と書きます。
    実行可
  2. 13
    computed — シグナルから作る派生値
    computed は、ほかのシグナルから自動で計算される読み取り専用のシグナルです。total = computed(() => price() * quantity()) と書くと、price か quantity が変わるたびに total も新しい値になります。読み方は普通のシグナルと同じで total() です。
    実行可
  3. 14
    effect — シグナルの変更に反応する
    effect は、シグナルが変わるたびに実行したい処理を登録する仕組みです。effect(() => { ... }) と書くと、その関数の中で読んでいるシグナルが自動で追跡され、どれかが変わるたびに関数が再実行されます。コンポーネントのコンストラクタの中で登録するのが基本です。
  4. 15
    input() — 親から値を受け取る
    親コンポーネントから子へデータを渡すには、子側で input() を宣言します。task = input.required<string>() と書くと、親はテンプレートで [task]="値" の形で値を渡せます。受け取った値はシグナルなので、子のテンプレートでは task() と関数として読みます。
  5. 16
    output() — 親へイベントを通知する
    子コンポーネントから親へ「ボタンが押された」などの出来事を伝えるには、子側で output() を宣言します。deleted = output<number>() と書いて deleted.emit(42) を呼ぶと、親はテンプレートの (deleted)="onDeleted($event)" でそれを受け取れます。$event にはemitした値が入ります。
  6. 17
    model() — 親と子で値を双方向に同期する
    親から受け取った値を子側からも書き換えたいときは、model() を使います。value = model(0) と宣言すると、子は value() で読み、value.set() や value.update() で書き換えられます。親はテンプレートで [(value)]="score" と書くだけで、どちらの変更ももう一方へ自動で反映されます。

サービスと通信

  1. 18
    サービスとDI — ロジックの置き場所
    画面の表示に直接関係しないロジック(データの管理、計算、ログ出力など)は、サービスというクラスに切り出します。@Injectable({ providedIn: 'root' }) を付けて定義すると、Angularがインスタンスを1つだけ作り、必要とするコンポーネントに渡してくれます。この「必要な部品をフレームワークが渡してくれる」仕組みを依存性の注入(DI)と呼びます。
  2. 19
    inject() — サービスを受け取る
    コンポーネントからサービスを使うには、inject() 関数でインスタンスを受け取ります。private cart = inject(CartService) とフィールドの初期化として書くだけで、DIコンテナが管理しているインスタンスが手に入ります。あとは this.cart.add() のように普通のメソッド呼び出しで使えます。
  3. 20
    HttpClient — APIとの通信
    サーバーのAPIからデータを取得するには HttpClient を使います。アプリ起動時の providers に provideHttpClient() を追加したうえで、コンポーネントやサービスで inject(HttpClient) して this.http.get<User[]>('/api/users') のように呼び出します。レスポンスは subscribe で受け取り、届いたデータを状態に反映します。
  4. 21
    Observable と RxJS — 時間とともに届く値
    前のレッスンの HttpClient は、結果を Observable という入れ物で返します。Observable は「これから(非同期に)届くかもしれない値の流れ」を表すもので、subscribe(購読)して初めて処理が動き、値が届くとコールバックが呼ばれます。1回きりの通信結果も、何度も発火するイベントも、同じ Observable として扱えるのが特徴です。
  5. 22
    ルーティング — URLで画面を切り替える
    ルーティングは、URLに応じて表示するコンポーネントを切り替える仕組みです。Routes型の配列に { path: 'about', component: AboutComponent } のようにURLとコンポーネントの対応を定義し、アプリ起動時に provideRouter(routes) で登録します。コンポーネントが表示される場所は <router-outlet /> で指定します。
  6. 23
    routerLink — 画面遷移のリンクを作る
    ルーティングを設定したら、ページ間を移動するリンクは <a href> ではなく <a routerLink="/about"> と書きます。routerLink を使うと、ページ全体を再読み込みせずに、必要な部分だけを差し替えて画面が切り替わります。これがシングルページアプリの軽快な遷移を支える仕組みです。href を使うとブラウザがページ全体を読み込み直してしまい、その良さが失われます。
  7. 24
    ルートパラメータ — URLから値を受け取る
    URLの一部を可変にして値を受け取るには、ルート定義のpathに 'users/:id' のようにコロン付きの名前を書きます。表示されたコンポーネント側では inject(ActivatedRoute) でルート情報を受け取り、route.snapshot.paramMap.get('id') で実際の値を取り出します。/users/7 にアクセスすれば '7' が得られます。
  8. 25
    プログラムでの画面遷移 — Router.navigate
    リンクのクリック以外、たとえば「ログインに成功したらトップへ移動する」のように、処理の結果に応じてコード側から画面を切り替えたいことがあります。そのときは inject(Router) で受け取った Router の navigate() を呼びます。this.router.navigate(['/users', id]) と書くと、配列の要素がつながって /users/7 のようなURLへ遷移します。

フォームとテンプレート機能

  1. 26
    ライフサイクルフック — ngOnInit と ngOnDestroy
    コンポーネントには「生まれてから消えるまで」の段階があり、その節目で呼ばれるメソッドをライフサイクルフックと呼びます。ngOnInit() は入力値が揃って表示の準備が整った直後に1回だけ呼ばれ、ngOnDestroy() は画面から取り除かれる直前に呼ばれます。クラスにメソッドとして定義しておくだけで、Angularが適切なタイミングで自動的に呼び出します。
  2. 27
    パイプ — 値を表示用に整形する
    パイプは、テンプレート内で値を「見せ方」に変換する仕組みです。{{ price | currency }} のように縦棒に続けてパイプ名を書くと、元の値はそのままに、表示だけが整形されます。date・currency・uppercase・lowercase・json・percent など、よく使う変換はAngularに最初から用意されています。
  3. 28
    カスタムパイプ — @Pipe で独自の整形を作る
    用意されたパイプで足りないときは、自分でパイプを作れます。@Pipe({ name: 'truncate' }) を付けたクラスに transform(value, ...args) メソッドを実装すると、テンプレートで {{ text | truncate:20 }} のように使えるようになります。transform の第1引数が変換前の値、2つ目以降がコロンで渡した引数で、戻り値が表示される値になります。
    実行可
  4. 29
    リアクティブフォーム — FormControl と FormGroup
    リアクティブフォームは、フォームの値や状態をTypeScript側のオブジェクトとして扱う仕組みです。1つの入力欄を FormControl('') で表し、複数をまとめた form = new FormGroup({ name: ..., email: ... }) で1つのフォームを構成します。テンプレートでは <form [formGroup]="form"> でフォーム全体を結び付け、各入力欄に formControlName="name" を書いて対応させます。
  5. 30
    フォームのバリデーション — Validators
    入力値が正しいかを検証するには、FormControl の第2引数に Validators を渡します。new FormControl('', [Validators.required, Validators.email]) と書くと、未入力ならエラー、メール形式でなければエラー、と判定されます。各コントロールやフォーム全体は valid / invalid というプロパティを持ち、検証結果に応じて true / false が切り替わります。
    実行可
  6. 31
    属性ディレクティブ — ngClass と ngStyle
    属性ディレクティブは、要素の見た目や振る舞いを後付けで変える仕組みです。ngClass は条件に応じて複数のCSSクラスをまとめて付け外しでき、[ngClass]="{ active: isActive, error: hasError }" のようにオブジェクトで「クラス名: 付けるか」を渡します。ngStyle も同様に [ngStyle]="{ color: textColor }" でスタイルをまとめて指定できます。

Angular v22の新機能

  1. 32
    シグナルフォーム — form() でフォームもシグナルに
    v22で正式版になったシグナルフォームは、フォームの値をシグナルのモデルとして持ち、form() でそこからフォームを組み立てる新しい方式です。signal({ email: '', password: '' }) のようにモデルを作り、form(model, schema) に渡すと、各項目に対応したフィールドのツリーができます。これまで学んだリアクティブフォーム(FormGroup / FormControl)の、シグナル時代版だと考えると分かりやすいです。
  2. 33
    resource() — 非同期データをシグナルで扱う
    v22で正式版になった resource() は、APIからの非同期取得をシグナルの世界に取り込む仕組みです。resource({ params, loader }) と書くと、params が返すシグナルの値が変わるたびに loader が自動で再実行され、結果がシグナルとして手に入ります。以前のように ngOnInit で subscribe して状態へ詰め替える定型コードが不要になります。
  3. 34
    ゾーンレスとOnPushの既定化
    v22では、新しく作るアプリから zone.js が標準で外れ、ゾーンレスがデフォルトになりました。これまでAngularは zone.js を使って「何か起きたかもしれない」タイミングで画面全体を見直していましたが、ゾーンレスでは更新のきっかけをシグナルなどの明示的な変更に絞ります。これがシグナル中心への移行の総仕上げにあたります。
  4. 35
    Angular Aria — アクセシビリティを部品で担保する
    v22で正式版になった Angular Aria は、タブ・メニュー・アコーディオンといった操作部品の「アクセシビリティの面倒な部分」をディレクティブとして提供する仕組みです。ARIA属性の付与、矢印キーでの移動、フォーカスの管理といった、自前で正しく実装するのが難しい部分をAngularが肩代わりしてくれます。