← 解説「Angular DevTools — 部品の中身を覗く拡張機能」に戻る
サンプルコードで身につける: Angular DevTools — 部品の中身を覗く拡張機能
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1コンポーネントツリーで部品の有無を確かめる
「表示されない」の最初の確認は、そもそも部品が生まれているかです。Angular DevTools のツリーに部品が無ければ @if の条件を疑えます。値の調査より先にツリーを見る癖を付けましょう。
// バグ: 管理者バッジ(app-admin-badge)が表示されない
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-profile',
template:
'<h2>プロフィール</h2>' +
'@if (role() === "admin") { <app-admin-badge /> }',
imports: [AdminBadge],
})
export class Profile {
// 原因: APIが返す値は 'Admin'(大文字始まり)なのに
// 'admin' と比較しているため @if が常に false
role = signal('Admin');
}
// Angular DevTools での調査:
// Components ツリー:
// app-profile ← ある
// (app-admin-badge が無い!)
// app-profile を選ぶと右側に role: 'Admin' と見える
// → 「値はAdmin、条件はadmin」の食い違いを目で確認できる
// 直し方: 比較を揃える(role().toLowerCase() === 'admin' 等)
2input の値を見て親子のどちらを疑うか決める
子の表示がおかしいとき、DevTools で子の input を見れば「親からの受け渡し」と「子の表示処理」のどちらが悪いか即決できます。この例では親のバインド忘れが input の undefined として現れます。
// バグ: 子コンポーネントの名前欄が空になる
import { Component, input } from '@angular/core';
@Component({
selector: 'app-user-card',
template: '<p>{{ user()?.name }}</p>',
})
export class UserCard {
user = input<{ name: string }>();
}
// 親テンプレート側(バグはここ):
// <app-user-card user="currentUser" />
// ^^^^ 原因: [ ] を忘れたので 'currentUser' という文字列を
// 渡そうとして型が合わず、実際には何も渡っていない
// Angular DevTools での調査:
// ツリーで app-user-card を選ぶ → 右側の表示:
// user (input) undefined
// → 子に届いた時点で空 = 悪いのは親からの受け渡し側と確定
// (もし user に正しい値が見えたら、疑うのは子のテンプレート)
//
// 直し方: [user]="currentUser" とプロパティバインディングにする
3「データは正しいのに表示がおかしい」を切り分ける
DevTools で signal の値が正しいと確認できれば、残る容疑者はテンプレートだけです。この例では値は正しく、パイプの指定ミスという表示側の原因にたどり着きます。
// バグ: 価格が「1980」と表示される(「1,980円」にしたい)
import { Component, signal } from '@angular/core';
import { DecimalPipe } from '@angular/common';
@Component({
selector: 'app-price',
// 原因: number パイプを書いたつもりが imports に入れ忘れて
// いたため、パイプなしのテンプレートに書き戻していた
template: '<p>{{ price() }}円</p>',
imports: [DecimalPipe],
})
export class Price {
price = signal(1980);
}
// Angular DevTools での切り分け:
// app-price を選ぶ → price (signal): 1980
// → データは正しい。悪いのは100%表示側と確定
// → テンプレートを見直す作業だけに集中できる
//
// 直し方: パイプを通す
// template: '<p>{{ price() | number }}円</p>'
// → 「1,980円」と表示される
4Profiler で重い描画の犯人を探す
入力のたびに画面がカクつくときは、Profiler で変更検知を録画します。棒グラフからどのコンポーネントに時間がかかったかが分かり、テンプレート内の重い関数呼び出しといった原因に絞り込めます。
// バグ: 検索欄に1文字打つたびに画面全体がカクつく
@Component({
selector: 'app-report',
// 原因: テンプレートから重い関数を直接呼んでいる
// 変更検知のたびに毎回再計算されてしまう
template: '<p>集計: {{ heavySummary() }}</p>',
})
export class Report {
heavySummary(): number {
let sum = 0;
for (let i = 0; i < 10_000_000; i++) { sum += i % 7; }
return sum;
}
}
// Profiler での調査:
// 1. Angular DevTools → Profiler → 録画開始
// 2. 検索欄に数文字入力 → 録画停止
// 3. 棒グラフ: 1打鍵ごとに変更検知が1本。app-report が毎回 120ms
// → 犯人は app-report の再計算と特定できる
//
// 直し方: computed に置き換えると依存が変わったときだけ再計算される
// summary = computed(() => this.calcFrom(this.rows()));
// template: '<p>集計: {{ summary() }}</p>'
5input を初回だけコピーするバグを見抜く
DevTools で「input は新しい値なのに表示が古い」と見えたら、子が input を別プロパティにコピーして使っている疑いがあります。input は都度読み出すか computed で派生させるのが正しい形です。
// バグ: 親でユーザーを切り替えても子の表示名が古いまま
import { Component, computed, input } from '@angular/core';
@Component({
selector: 'app-greeting',
template: '<p>{{ displayName }}</p>',
})
export class Greeting {
name = input('');
// 原因: 生成時に1回だけ input を読んでコピーしている
// その後 input が変わってもこの値は更新されない
displayName = this.name() + ' さん';
}
// Angular DevTools での見え方:
// name (input) '田中' ← 新しい値がちゃんと届いている
// displayName '佐藤 さん' ← 表示用の値だけ古い
// → 「届いているのに反映されない」= コピーの断絶を疑う
//
// 直し方: computed で派生させれば input の変化に追従する
// displayName = computed(() => this.name() + ' さん');
// テンプレートは {{ displayName() }} にする