← 解説「contentChild() — 差し込まれた中身をクラスから掴む」に戻る
サンプルコードで身につける: contentChild() — 差し込まれた中身をクラスから掴む
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1view と content の区別を1つの部品で見る
同じコンポーネントの中でも、自分のtemplateに書いたものはview、タグの間に差し込まれたものはcontentです。取得に使う関数もviewChildとcontentChildで対になっています。
// ── card.component.ts ──
// template:
// '<h2 #heading>カード</h2> ← view(自分で書いた)
// <ng-content />' ← content の差し込み口
//
// ── 利用側 ──
// <app-card>
// <app-badge /> ← content(差し込まれた)
// </app-card>
//
// ── card.component.ts のクラス側 ──
// heading = viewChild<ElementRef>('heading'); // viewから探す
// badge = contentChild(BadgeComponent); // contentから探す
//
// viewChild で content は取れないし、その逆も取れない
2contentChild の基本形 — 差し込まれた部品を1つ取る
カードに差し込まれたタイトル部品を取得し、有無で表示を変える例です。差し込まれていない可能性があるので、戻り値はundefinedを含むシグナルになります。
import { Component, contentChild, computed } from '@angular/core';
import { CardTitleComponent } from './card-title.component';
@Component({
selector: 'app-card',
template:
'@if (!hasTitle()) { <h2>無題のカード</h2> }' +
'<ng-content />',
})
export class CardComponent {
// 差し込まれた CardTitleComponent を探す(無ければ undefined)
title = contentChild(CardTitleComponent);
hasTitle = computed(() => this.title() !== undefined);
}
3contentChildren でタブを束ねる
タブUIの定番実装です。利用側が並べたパネルをcontentChildrenで集め、その数だけボタンを描画します。パネルが増減してもシグナルなので自動で追従します。
import { Component, contentChildren, signal } from '@angular/core';
import { TabPanelComponent } from './tab-panel.component';
@Component({
selector: 'app-tabs',
template:
'@for (panel of panels(); track panel; let i = $index) {' +
' <button (click)="active.set(i)">{{ panel.title() }}</button>' +
'}' +
'<ng-content />',
})
export class TabsComponent {
panels = contentChildren(TabPanelComponent);
active = signal(0);
}
// 利用側:
// <app-tabs>
// <app-tab-panel title="基本情報">...</app-tab-panel>
// <app-tab-panel title="設定">...</app-tab-panel>
// </app-tabs>
4テンプレート参照変数の名前でも探せる
取りたい対象はコンポーネントの型だけでなく、#で付けたテンプレート参照変数の名前(文字列)でも指定できます。ただの要素を差し込んでもらう場合に便利です。
import { Component, contentChild, ElementRef, effect } from '@angular/core';
@Component({
selector: 'app-form-field',
template: '<label>ラベル</label><ng-content />',
})
export class FormFieldComponent {
// 利用側が #input を付けた要素を探す
input = contentChild<ElementRef<HTMLInputElement>>('input');
constructor() {
effect(() => {
// 差し込まれた入力欄に自動でフォーカスする
this.input()?.nativeElement.focus();
});
}
}
// 利用側:
// <app-form-field><input #input type="text" /></app-form-field>
5必須の差し込みは required で宣言する
「この部品は必ず差し込まれる前提」なら、contentChild.requiredを使います。戻り値の型からundefinedが消えて毎回のチェックが不要になり、差し込み忘れは実行時エラーとしてすぐ気づけます。input.required()と同じ考え方です。
import { Component, contentChild, computed } from '@angular/core';
import { StepLabelComponent } from './step-label.component';
@Component({
selector: 'app-wizard-step',
template: '<ng-content />',
})
export class WizardStepComponent {
// 必ず差し込まれる前提。型は undefined を含まない
label = contentChild.required(StepLabelComponent);
// ?. なしでそのまま使える
labelText = computed(() => this.label().text());
}
// 差し込み忘れると実行時にエラーで知らせてくれるので、
// 「なぜか動かない」ではなく「忘れている」とすぐ分かる