Dev Study
← 解説「コンポーネントを追加すると何が変わるか — ビルド時の変化」に戻る

サンプルコードで身につける: コンポーネントを追加すると何が変わるか — ビルド時の変化

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

1追加前後で ɵcmp がどう変わるか

子コンポーネントをimportsに追加する前後で、親のコンパイル結果に起きる差分を概念コードで比べます。「参照が1本増える」ことがすべての起点です。

// ── 追加前: PageComponent.ɵcmp(概念)──
// dependencies: []
// template: 作成モードで <main> と <h1> を作る命令のみ

// ── UserCardComponent を imports してテンプレートで使うと ──
// dependencies: [UserCardComponent]  ← クラス参照が増えた
// template: 作成モードに
//   「app-user-card のホスト要素を作る」
//   「UserCardComponent をこの位置で起動する」
//   命令が追加される
//
// この dependencies の参照こそが、
//   ・バンドラーが「同梱すべき」と判断する根拠であり
//   ・実行時に子の設計図(ɵcmp)へ到達する道になる

2NG8001 — 照合失敗エラーの正体

importsに書き忘れたときのお馴染みのエラーを、内部機構の言葉で読み直します。「実行時に部品が見つからない」のではなく「ビルド時の突き合わせで相手がいなかった」というエラーです。

// NG8001: 'app-user-card' is not a known element
//
// コンパイラの思考の流れ:
//   1. <app-user-card> というタグを見つけた
//   2. 標準のHTML要素リストに無い
//   3. この component の imports の selector とも一致しない
//   4. → 照合先が無いので報告する
//
// つまり直し方は「照合候補に載せる」の一択:
@Component({
  imports: [UserCardComponent], // ← これで照合が通る
  template: '<app-user-card />',
})
export class PageComponent {}
//
// 逆パターンの NG8113(imports に書いたが未使用)は
// 「照合候補に載せたのに一度もマッチしなかった」という警告

3セレクタ照合はビルド時 — だからタグ名を動的にできない

「タグ名を変数にして出し分けたい」が素直に書けないのは、照合がビルド時に終わっているからです。動的にコンポーネントを選びたいときの正攻法(ViewContainerRef.createComponent)も合わせて押さえます。

// × こういうことはできない(照合はビルド時に済んでいるため)
//   template: '<{{ tagName() }} />'

// ○ 実行時にコンポーネントを選びたいときの正攻法:
//   クラス参照を渡して「ここに生やして」と依頼する
import { Component, viewChild, ViewContainerRef, inject } from '@angular/core';
import { LineChartComponent } from './line-chart.component';
import { BarChartComponent } from './bar-chart.component';

@Component({
  selector: 'app-report',
  template: '<ng-container #outlet />',
})
export class ReportComponent {
  private outlet = viewChild.required('outlet', {
    read: ViewContainerRef,
  });

  show(type: 'line' | 'bar') {
    const cmp = type === 'line' ? LineChartComponent : BarChartComponent;
    this.outlet().clear();
    this.outlet().createComponent(cmp); // 誕生手順を明示的に起動
  }
}

4参照グラフとチャンク分割の関係

同じ部品でも「どこから参照されるか」で入るチャンクが変わります。ビルド出力のファイル一覧と参照の関係を対応づけて読むと、初期表示に響く追加かどうかを判断できます。

ng build
# Initial chunk files:
#   main-XXXX.js      512.3 kB   ← 起動時に必ず読む
# Lazy chunk files:
#   chunk-ADMIN.js     84.1 kB   ← 遅延ルートで初めて読む

# UserCardComponent をどこに追加したかで行き先が決まる:
#
#   AppComponent(初期表示)から参照
#     → main に入る。初期バンドルが太る
#
#   遅延ルートの AdminPage からだけ参照
#     → chunk-ADMIN に入る。初期表示への影響ゼロ
#
#   両方から参照
#     → 共有チャンクに切り出されることもある
#
# 「追加=参照グラフに節点を足す」なので、
# 参照元がチャンクの行き先を決める

5追加の全行程を1枚にまとめる

ng generateからブラウザ表示までの全行程を俯瞰します。次のレッスン以降で学ぶ実行時の話がどこに接続するのかも、この地図で位置づけられます。

// ── コンポーネント追加の全行程 ──
//
// [開発]   ng generate component user-card
//    ↓     imports に追加、テンプレートで使用
//
// [ビルド] セレクタ照合(NG8001はここで出る)
//    ↓     ɵcmp 生成 + 親の依存リストに参照追加
//    ↓     参照グラフに従ってチャンクへ配置
//
// [配信]   ただのJSファイルとしてブラウザへ
//    ↓
// [実行時] 親の作成モードが子のホスト要素を作る
//    ↓     誕生手順(DI→input→フック→再帰)…次レッスン
//    ↓     変更検知が更新モードを走らせる …後のレッスン
//          破棄の逆再生               …最後のレッスン