Dev Study
← 解説「ng-content — 中身を差し込めるコンポーネント」に戻る

サンプルコードで身につける: ng-content — 中身を差し込めるコンポーネント

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

1最小の ng-content

子のテンプレートに <ng-content /> を置くと、親がタグで囲んだ中身がその位置に差し込まれます。「枠は子が持ち、中身は親が決める」というコンテンツ投影の核だけを示した最小の形です。共通の枠を持つ部品づくりはすべてここから始まります。

import { Component } from '@angular/core';

@Component({
  selector: 'app-box',
  // <ng-content /> の位置に、親が囲んだ中身が差し込まれる
  template: '<div class="box"><ng-content /></div>',
  styles: ['.box { border: 2px solid steelblue; padding: 12px; }'],
})
export class BoxComponent {}

// 親側:
// <app-box>この文章が枠の中に表示される</app-box>

2カード部品に任意のHTMLを差し込む

ng-content には文字列だけでなく、見出し・段落・画像など任意のHTMLをそのまま流し込めます。input の文字列で中身を渡す方式では表現できない構造も渡せるのが強みです。実務のカードやパネルはほぼこの形で作られています。

import { Component } from '@angular/core';

@Component({
  selector: 'app-card',
  // 余白・影・角丸などの共通の枠だけをこの部品が持つ
  template: '<div class="card"><ng-content /></div>',
  styles: [
    '.card { border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); }',
  ],
})
export class CardComponent {}

// 親側は見出しも段落もボタンも自由に入れられる:
// <app-card>
//   <h2>今月のお知らせ</h2>
//   <p>キャンペーンは6月30日までです。</p>
//   <button>詳しく見る</button>
// </app-card>

3select で複数のスロットに振り分ける

<ng-content select="..." /> と書くと、セレクタに一致した要素だけがその口に差し込まれ、残りは select なしの口へ入ります。ヘッダー・本文・フッターのように差し込み位置を分けたいダイアログやカードで使う形です。振り分けのルールはCSSセレクタと同じ書き方です。

import { Component } from '@angular/core';

@Component({
  selector: 'app-panel',
  // select に一致した要素はその口へ、残りは select なしの口へ入る
  template:
    '<section>' +
    '  <header><ng-content select=".panel-title" /></header>' +
    '  <div class="body"><ng-content /></div>' +
    '</section>',
})
export class PanelComponent {}

// 親側: class="panel-title" を付けた要素だけ header に入る
// <app-panel>
//   <h2 class="panel-title">設定</h2>
//   <p>ここは本文の口に差し込まれる</p>
// </app-panel>

4フォールバック内容 — 何も渡されなかったとき

<ng-content>...</ng-content> とタグの間に中身を書いておくと、親が何も差し込まなかったときだけその内容が表示されます。「基本はデフォルトの表示、必要なら差し替え」という部品が作れます。汎用部品のデフォルト文言によく使われる形です。

import { Component } from '@angular/core';

@Component({
  selector: 'app-empty-state',
  // タグの間に書いた内容は、親が何も渡さなかったときだけ表示される
  template:
    '<div class="empty">' +
    '  <ng-content>データがありません</ng-content>' +
    '</div>',
})
export class EmptyStateComponent {}

// <app-empty-state /> → 「データがありません」と表示
// <app-empty-state>検索結果は0件でした</app-empty-state>
//   → 渡した内容で上書きされる

5ボタン部品のラッパーを作る

見た目を統一したボタン部品でも、ラベルは使う側ごとに変えたいので ng-content で受け取ります。アイコン付きのラベルなどもそのまま渡せるため、input で文字列を受け取るより柔軟です。デザインを共通化したいUI部品ライブラリの定番パターンです。

import { Component } from '@angular/core';

@Component({
  selector: 'app-primary-button',
  // 見た目は部品が持ち、ラベルは親から差し込んでもらう
  template: '<button class="primary"><ng-content /></button>',
  styles: [
    '.primary { background: royalblue; color: white; border: none; padding: 8px 16px; border-radius: 4px; }',
  ],
})
export class PrimaryButtonComponent {}

// 使う側はラベルだけ決めればよい:
// <app-primary-button>保存する</app-primary-button>
// <app-primary-button>次へ進む</app-primary-button>