Dev Study
← 解説「エラーメッセージの解剖 — NGコードとスタックトレース」に戻る

サンプルコードで身につける: エラーメッセージの解剖 — NGコードとスタックトレース

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

1NG0201: provider が見つからない

NG02xx はDIまわりのエラーです。HttpClient を inject しているのに provideHttpClient の登録を忘れると NG0201 が出ます。エラーコードから「注入の設定漏れ」と当たりを付けられれば、直す場所は app.config.ts だと分かります。

// ERROR RuntimeError: NG0201: No provider found for _HttpClient!

import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-user-list',
  template: '<p>ユーザー一覧</p>',
})
export class UserList {
  // 原因: HttpClient を注入しているが、アプリに提供元が未登録
  private http = inject(HttpClient);
}

// 直し方: app.config.ts に provideHttpClient() を追加する
//
// export const appConfig: ApplicationConfig = {
//   providers: [provideHttpClient()],  // ← これを足す
// };

2NG0100: 変更検知後に値が変わった

NG01xx はテンプレート・変更検知まわりのエラーです。描画が終わった直後のフックで値を書き換えると NG0100 が出ます。シグナルを使い、値の更新をAngularに正しく伝えるのが現代的な直し方です。

// ERROR RuntimeError: NG0100: ExpressionChangedAfterItHasBeenCheckedError

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

@Component({
  selector: 'app-header',
  template: '<h1>{{ title }}</h1>',
})
export class Header implements AfterViewInit {
  title = '読み込み中';

  ngAfterViewInit() {
    // 原因: 描画チェックが終わった直後に、ただのプロパティを書き換えた
    // Angularは「検査後に値が変わった」ことを開発モードで検出して怒る
    this.title = 'ダッシュボード';
  }
}

// 直し方: シグナルにすると更新が正しく変更検知に伝わる
//   title = signal('読み込み中');
//   ngAfterViewInit() { this.title.set('ダッシュボード'); }
//   テンプレートは {{ title() }} にする

3NG0200: サービス同士の循環依存

サービスAがBを、BがAを注入し合うと NG0200 が出ます。エラーコードだけで「依存の輪ができている」と分かるのがNGコードの便利なところです。共通部分を第三のサービスに切り出して輪を断ち切ります。

// ERROR RuntimeError: NG0200: Circular dependency detected for CartService

import { Injectable, inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CartService {
  // 原因その1: CartService が StockService に依存し…
  private stock = inject(StockService);
}

@Injectable({ providedIn: 'root' })
export class StockService {
  // 原因その2: StockService も CartService に依存している(輪が完成)
  private cart = inject(CartService);
}

// 直し方: 両者が必要とする共通処理を第三のサービスに切り出し、
// 依存を CartService → 共通 ← StockService の一方向にする

4スタックトレースから自分の行を探す

スタックトレースの大半は Angular 内部のファイルで、読む必要はありません。src/app 配下のファイル名が出ている行だけを探し、そこを調査の起点にします。上にある行ほどエラー発生地点に近いことも覚えておきましょう。

// ERROR TypeError: Cannot read properties of undefined (reading 'name')
//
//   at UserCard_Template (user-card.html:3)      ← ②テンプレート3行目
//   at executeTemplate (core.mjs:11249)           ← Angular内部(読み飛ばす)
//   at refreshView (core.mjs:12780)               ← Angular内部(読み飛ばす)
//   at detectChangesInView (core.mjs:13004)      ← Angular内部(読み飛ばす)
//   at UserList.loadUsers (user-list.ts:24)      ← ①自分のファイル!
//
// 読み方:
//   1. src/app 配下のファイル名(user-card.html / user-list.ts)だけ探す
//   2. 上の行ほどエラー発生地点に近い
//      → user-card.html の3行目で undefined の name を読もうとした
//   3. 「なぜ undefined だったか」を user-list.ts:24 に遡って調べる
//
// この例の典型的な直し方: データ到着前の描画に備えて
//   @if (user(); as u) { {{ u.name }} } のように守る

5エラーコードで公式解説を引く

NGコードは angular.dev/errors の該当ページに直結しています。エラー文全体で検索するより、コードをURLに付けて公式解説を開くほうが速くて正確です。解説には典型的な原因と直し方が載っています。

// エラーコードから公式解説へたどる手順
//
// コンソールの表示:
//   ERROR RuntimeError: NG0203: inject() must be called from
//   an injection context ...
//
// 1. コード部分(NG0203)だけを抜き出す
// 2. URLに付けて開く:
//    https://angular.dev/errors/NG0203
// 3. 解説ページで分かること:
//    ・意味: inject() を注入コンテキスト外で呼んだ
//    ・典型例: クラスの外や setTimeout の中で inject() を呼んだ
//    ・直し方: フィールド初期化子かコンストラクタで呼ぶ
//
// 悪い例(NG0203 が出る):
//   onClick() { const http = inject(HttpClient); }  // メソッド内はダメ
//
// 直した例:
//   private http = inject(HttpClient);  // フィールド初期化子ならOK