Angularのコンポーネントとは、@Component デコレーターを付けたTypeScriptのクラスに、テンプレート(HTML)とスタイル(CSS)を組み合わせた画面の部品です。 ng generate component 名前 で作り、使う側のコンポーネントの imports に入れて、テンプレートにセレクター(タグ名)を書けば表示されます。現在の Angular では、コンポーネントは何も指定しなくても standalone(NgModule に登録しなくても単体で使える形式)です。

この記事では、Angular 22.2.1 で新規プロジェクトを作り、すべてのコードをビルドして Chromium で表示した結果を載せています。ng g c が生成する4つのファイルを1つずつ読み解き、@Component の全プロパティを一覧にしました。imports に入れ忘れたときの「NG8001: is not a known element」「NG8002」などのビルドエラーも実際に出して、直し方を示します。インラインテンプレートと外部ファイルの違い、:host とスタイルのカプセル化(スタイルの効く範囲を部品の中に閉じる仕組み)の3モードも、同じ条件で比べました。

確認環境: Linux(x64)/Node.js 24.21.0/Angular CLI 22.2.1・Angular 22.2.1/TypeScript 6.0.3/Vitest 5.0.3/Playwright 1.56.0(Chromium)。出力中のパスは /home/taro/ に置き換えています。

Angularコンポーネントの早見表

やりたいこと 書き方 補足
コンポーネントを作る ng g c user-card user-card/ に .ts・.html・.css・.spec.ts の4ファイル
1ファイルで作る ng g c badge -t -s --skip-tests テンプレートとスタイルを .ts の中に書く
別のコンポーネントで使う imports: [UserCard] + テンプレートに <app-user-card /> imports に入れないと NG8001
タグ名を決める selector: 'app-user-card' 接頭辞 app は ng new --prefix で変更できる
HTML を外部ファイルにする templateUrl: './user-card.html' template と同時には書かない
HTML を直接書く template: `...` 短いテンプレート向き
CSS を外部ファイルにする styleUrl: './user-card.css' 複数なら styleUrls: [...]
CSS を直接書く styles: `...` 文字列または文字列の配列
親から値を受け取る name = input.required<string>() 渡し忘れると NG8008
親へイベントを送る selected = output<string>() 親は (selected)="..." で受ける
自分自身(ホスト要素)を装飾する :host { display: block; } 条件付きは :host(.active)
ホスト要素にクラスやイベントを付ける host: { '[class.pressed]': '...' } デコレーターの host プロパティ
スタイルの効く範囲を変える encapsulation: ViewEncapsulation.None 既定は Emulated
NgModule 形式にする standalone: false 既定は true(書かなくてよい)

覚えることは3つです。「クラスに @Component を付ける」「使う側の imports に入れる」「テンプレートにセレクターを書く」。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angularコンポーネントの作り方|standaloneとimportsの基本」

ng g cが生成する4つのファイルを読む

プロジェクトのフォルダーで次のコマンドを実行します(ng g c は ng generate component の短縮形)。

ng g c product-list
CREATE src/app/product-list/product-list.css (0 bytes)
CREATE src/app/product-list/product-list.spec.ts (568 bytes)
CREATE src/app/product-list/product-list.ts (208 bytes)
CREATE src/app/product-list/product-list.html (27 bytes)

product-list.ts(クラスとデコレーター)

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

@Component({
  imports: [],
  selector: 'app-product-list',
  styleUrl: './product-list.css',
  templateUrl: './product-list.html',
})
export class ProductList {}
行 意味
@Component({...}) このクラスがコンポーネントであることと、その設定を Angular に伝える
imports: [] このテンプレートの中で使う、ほかのコンポーネント・ディレクティブ・パイプを並べる場所
selector: 'app-product-list' テンプレートに書くタグ名。<app-product-list /> と書いた場所にこの部品が入る
styleUrl この部品専用の CSS ファイル
templateUrl この部品の HTML ファイル
export class ProductList {} 状態(プロパティ)と処理(メソッド)を書くクラス

standalone: true はどこにも書かれていません。 公式 API リファレンスでは standalone の既定値が true とされており、書かなくても standalone として扱われます。クラス名は ProductListComponent ではなく ProductList、ファイル名は product-list.component.ts ではなく product-list.ts で生成されます。

product-list.html(テンプレート)

<p>product-list works!</p>

生成直後は1行だけです。ここに表示したい HTML を書き、{{ 式 }} の形でクラスの値を埋め込みます。

product-list.css(スタイル)

0バイトの空ファイルです。ここに書いた CSS は、この部品のテンプレートの中だけに効きます(後半の「カプセル化」で実際に確かめます)。

product-list.spec.ts(テスト)

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductList } from './product-list';

describe('ProductList', () => {
  let component: ProductList;
  let fixture: ComponentFixture<ProductList>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ProductList],
    }).compileComponents();

    fixture = TestBed.createComponent(ProductList);
    component = fixture.componentInstance;
    await fixture.whenStable();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
部分 意味
TestBed.configureTestingModule({ imports: [ProductList] }) テスト用の環境にこのコンポーネントを読み込む。ここも imports
TestBed.createComponent(ProductList) コンポーネントを1つ作り、操作用の fixture を受け取る
fixture.componentInstance クラスのインスタンス
await fixture.whenStable() 描画が落ち着くまで待つ
it('should create', ...) 「作れること」だけを確かめる最小のテスト

@Componentのプロパティ一覧

公式 API リファレンス(angular.dev/api/core/Component)に載っているプロパティの一覧です。上の4つ(selector・imports・template/templateUrl・styles/styleUrl)で、ほとんどの部品は書けます。

プロパティ 型 役割 既定値・補足
selector string テンプレートでこの部品を表す CSS セレクター タグ名のほか、属性・クラスのセレクターも使える
imports 配列 テンプレートで使うコンポーネント・ディレクティブ・パイプ standalone のときに使う
template string インラインのテンプレート templateUrl とどちらか一方
templateUrl string テンプレートファイルのパス 相対パスで書く
styles string または string の配列 インラインの CSS ―
styleUrl string CSS ファイル1つのパス ―
styleUrls string の配列 CSS ファイル複数のパス ―
changeDetection ChangeDetectionStrategy 変更検知(画面を更新するタイミングの判断)の方式 既定は OnPush
encapsulation ViewEncapsulation スタイルのカプセル化の方式 既定は Emulated
standalone boolean NgModule なしで使えるか 既定は true。NgModule に入れるときだけ false
host オブジェクト ホスト要素へのプロパティ・属性・イベントの割り当て '[class.x]'・'(click)' など
providers Provider の配列 この部品と子孫に提供するサービス ―
viewProviders Provider の配列 この部品のビュー内の子にだけ提供するサービス ―
hostDirectives 配列 ホスト要素に適用する standalone ディレクティブ ―
inputs 配列 入力プロパティをデコレーター側で列挙する 通常は input() で足りる
outputs string の配列 出力プロパティをデコレーター側で列挙する 通常は output() で足りる
queries オブジェクト 子要素の取得(クエリー)をデコレーター側で設定する ―
exportAs string テンプレート変数に割り当てるときの名前 ―
animations 配列 アニメーションの trigger() 定義 ―
preserveWhitespaces boolean テンプレートの余分な空白を残すか ―
schemas 配列 未知の要素・属性を許可するスキーマ CUSTOM_ELEMENTS_SCHEMA など
jit true AOT コンパイラーの対象から外し、実行時にコンパイルする ―

コンポーネントを作って親から使う

ここからは、会員カードを表示する UserCard を作り、ルートコンポーネント App から使います。3つのファイルと App をコピーすれば、そのまま動きます。

ng g c user-card

src/app/user-card/user-card.ts

import { Component, computed, input, output } from '@angular/core';

@Component({
  imports: [],
  selector: 'app-user-card',
  styleUrl: './user-card.css',
  templateUrl: './user-card.html',
})
export class UserCard {
  readonly name = input.required<string>();
  readonly points = input(0);
  readonly selected = output<string>();

  protected readonly rank = computed(() => (this.points() >= 100 ? 'ゴールド' : 'レギュラー'));

  protected select(): void {
    this.selected.emit(this.name());
  }
}

src/app/user-card/user-card.html

<article class="card">
  <h2>{{ name() }}</h2>
  <p>ポイント: {{ points() }}({{ rank() }})</p>
  <button type="button" (click)="select()">選ぶ</button>
</article>

src/app/user-card/user-card.css

:host {
  display: block;
  border: 1px solid #ccc;
  padding: 8px;
}
:host(.active) {
  border-color: rgb(0, 128, 0);
}
h2 {
  color: rgb(0, 0, 255);
}

使う側の src/app/app.ts と src/app/app.html です。import 文でクラスを読み込み、imports 配列に入れる、の2か所がそろって初めてテンプレートで使えます。

import { Component, signal } from '@angular/core';
import { UserCard } from './user-card/user-card';

@Component({
  imports: [UserCard],
  selector: 'app-root',
  styleUrl: './app.css',
  templateUrl: './app.html',
})
export class App {
  protected readonly title = signal('shop-app');
  protected readonly picked = signal('なし');
}
<h1>{{ title() }}</h1>
<app-user-card name="佐藤" [points]="120" class="active" (selected)="picked.set($event)" />
<app-user-card name="鈴木" />
<p id="picked">選択中: {{ picked() }}</p>
テンプレートの書き方 意味
name="佐藤" 文字列をそのまま渡す
[points]="120" 式の結果(ここでは数値 120)を渡す
(selected)="picked.set($event)" 子の output を受け取る。$event に emit() した値が入る
class="active" ホスト要素にふつうの HTML 属性として付く
<app-user-card ... /> 中身がなければ自己終了タグで書ける

ビルドして Chromium で開き、表示と計算後のスタイルを読み取った結果です。

1枚目: 佐藤 / ポイント: 120(ゴールド) / 選ぶ   枠線 rgb(0, 128, 0)   h2 の色 rgb(0, 0, 255)
2枚目: 鈴木 / ポイント: 0(レギュラー) / 選ぶ   枠線 rgb(204, 204, 204)
App の h1 の色: rgb(0, 0, 0)

クリック前: 選択中: なし
1枚目の「選ぶ」をクリック後: 選択中: 佐藤

points を渡さなかった2枚目は既定値の 0 になり、class="active" を付けた1枚目だけ枠線が緑になりました。UserCard の CSS に書いた h2 { color: blue } は、App の h1 には影響していません。

importsを入れ忘れたときのエラーと直し方

NG8001とNG8002を再現する

App の imports を空にして(imports: [])ビルドすると、次のエラーが出ました。

✘ [ERROR] NG8001: 'app-user-card' is not a known element:
1. If 'app-user-card' is an Angular component, then verify that it is included in the '@Component.imports' of this component.
2. If 'app-user-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message. Find more at https://next.angular.dev/errors/NG8001 [plugin angular-compiler]

    /home/taro/shop-app/src/app/app.html:2:0:
      2 │ <app-user-card name="佐藤" [points]="120" class="active" (selecte...
        ╵ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  Error occurs in the template of component App.
✘ [ERROR] NG8002: Can't bind to 'points' since it isn't a known property of 'app-user-card'.
1. If 'app-user-card' is an Angular component and it has 'points' input, then verify that it is included in the '@Component.imports' of this component.
2. If 'app-user-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@Component.schemas' of this component. Find more at https://next.angular.dev/errors/NG8002 [plugin angular-compiler]

同じビルドで、次のエラーも一緒に出ました。app-user-card がコンポーネントとして認識されないため、(selected) がふつうの DOM イベント扱いになり、$event の型が Event になったためです。

✘ [ERROR] TS2345: Argument of type 'Event' is not assignable to parameter of type 'string'. [plugin angular-compiler]

3種類のエラーが出ていますが、原因は1つ(imports の入れ忘れ)です。 メッセージの「verify that it is included in the '@Component.imports' of this component」のとおり、直し方は次の2行です。

// 直す前
import { Component, signal } from '@angular/core';

@Component({
  imports: [],
  // ...
})

// 直した後
import { Component, signal } from '@angular/core';
import { UserCard } from './user-card/user-card';   // 1. クラスを読み込む

@Component({
  imports: [UserCard],                               // 2. imports に入れる
  // ...
})

コンポーネントまわりのエラー早見表

imports を正しくしたうえで、ほかの書き間違いも1つずつ試しました。エラー文はすべて実際のビルド出力です。

エラー 出したときの書き方 原因 直し方
NG8001: 'app-user-card' is not a known element imports: [] のまま <app-user-card> を使う imports の入れ忘れ、またはセレクターの打ち間違い imports にクラスを入れる。タグ名を selector と見比べる
NG8002: Can't bind to 'points' since it isn't a known property of 'app-user-card'. 同上 imports の入れ忘れ 同上
NG8002: Can't bind to 'pointz' since it isn't a known property of 'app-user-card'. [pointz]="120" 入力名の打ち間違い input() の名前に合わせる
NG8008: Required input 'name' from component UserCard must be specified. <app-user-card /> input.required の値を渡していない name="..." を渡す
NG8004: No pipe found with name 'price'. {{ 1200 | price }} パイプを imports に入れていない imports にパイプのクラスを入れる
NG2011: The component 'Badge' appears in 'imports', but is not standalone and cannot be imported directly. It must be imported via an NgModule. 子に standalone: false を書く NgModule 形式の部品を直接 imports した standalone: false を外す。または NgModule 経由で読み込む
NG2008: Could not find template file './user-card.htm'. templateUrl のパス違い ファイル名・拡張子の打ち間違い 相対パスを確認する
NG8113: Highlight is not used within the template of App(警告) 使っていないものを imports に入れる 余分な imports 配列から外す

ビルドエラーにならない入れ忘れもあります。 属性セレクターのディレクティブ(<p appHighlight> など)は、imports に入れなくてもビルドが通りました。appHighlight がただの HTML 属性として扱われるためです。「エラーは出ないのに効かない」ときも、まず imports を見直してください。

インラインテンプレートと外部ファイルの違い

-t(--inline-template)と -s(--inline-style)を付けると、1ファイルにまとまったコンポーネントが生成されます。

ng g c badge -t -s --skip-tests
CREATE src/app/badge/badge.ts (192 bytes)
import { Component } from '@angular/core';

@Component({
  imports: [],
  selector: 'app-badge',
  styles: ``,
  template: ` <p>badge works!</p> `,
})
export class Badge {}

これを小さなバッジに書き換えます。バッククォートで囲むので、HTML も CSS も複数行で書けます。

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

@Component({
  selector: 'app-badge',
  template: `<span class="badge">{{ label() }}</span>`,
  styles: `
    :host {
      display: inline-block;
    }
    .badge {
      background: rgb(255, 215, 0);
      padding: 2px 6px;
    }
  `,
})
export class Badge {
  readonly label = input('NEW');
}

App の imports に Badge を足し、テンプレートに次の2行を書いて表示しました。

<app-badge />
<app-badge label="SALE" />
1つ目: NEW   display: inline-block   背景 rgb(255, 215, 0)
2つ目: SALE  display: inline-block   背景 rgb(255, 215, 0)
比べる点 インライン(template / styles) 外部ファイル(templateUrl / styleUrl)
ファイル数 1つ 3つ(+テスト)
生成コマンド ng g c badge -t -s ng g c user-card
向いている大きさ テンプレートが数行〜十数行の小さな部品 行数が多い、HTML と CSS を分けて編集したい部品
一覧性 クラスと見た目を1画面で読める 役割ごとにファイルが分かれる
動作 同じ 同じ
組み合わせ template + styleUrl のように片方だけインラインにもできる ―

どちらで書いても、ビルド後の動作は同じです。「スクロールせずに読めるならインライン、それ以上なら外部ファイル」を目安にすると決めやすくなります。

:hostとスタイルのカプセル化を比べる

:hostでコンポーネント自身を装飾する

コンポーネントのタグ(<app-user-card> そのもの)を ホスト要素と呼びます。ホスト要素は自分のテンプレートの外側にあるので、ふつうのセレクターでは指定できません。そこで :host を使います。

:host { display: block; }            /* ホスト要素そのもの */
:host(.active) { border-color: green; }  /* ホスト要素に active クラスがあるとき */

先ほどの UserCard で読み取った値です。

対象 読み取った値
1枚目(class="active")の display block
1枚目の枠線の色 rgb(0, 128, 0)(:host(.active) が効いている)
2枚目の枠線の色 rgb(204, 204, 204)(:host の #ccc)

カスタム要素の display は、指定しなければインラインです。 幅や余白を持たせたい部品は、:host { display: block; } を最初に書いておくとレイアウトが安定します(ng g c 名前 --display-block で自動的に入ります)。

カプセル化の3モードを同じ条件で比べる

encapsulation を変えた3つのコンポーネントを1ファイルに用意し、それぞれ p 要素に別々のスタイルを当てます。

// src/app/enc.ts
import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-enc-emulated',
  template: `<p>Emulated の p</p>`,
  styles: `p { color: rgb(255, 0, 0); }`,
})
export class EncEmulated {}

@Component({
  selector: 'app-enc-none',
  template: `<p>None の p</p>`,
  styles: `p { text-decoration: underline; }`,
  encapsulation: ViewEncapsulation.None,
})
export class EncNone {}

@Component({
  selector: 'app-enc-shadow',
  template: `<p>ShadowDom の p</p>`,
  styles: `p { font-weight: 700; }`,
  encapsulation: ViewEncapsulation.ShadowDom,
})
export class EncShadow {}

App の imports に3つを足し、テンプレートに App 自身の p も置きます。

<p id="outer">App の p</p>
<app-enc-emulated />
<app-enc-none />
<app-enc-shadow />

Chromium で各 p の計算後のスタイルを読み取った結果です。

どの p か 文字色(Emulated が赤を指定) 下線(None が指定) 太さ(ShadowDom が 700 を指定) p に付いた属性
App の p 黒 rgb(0, 0, 0) あり 400 id
Emulated の p 赤 rgb(255, 0, 0) あり 400 _ngcontent-ng-c2462001973
None の p 黒 あり 400 なし
ShadowDom の p 黒 あり 700 なし(シャドウツリーの中)

読み取れることは3つです。

  • Emulated(既定):赤は自分の p だけに効きました。Angular が要素に _ngcontent-ng-c... という属性を付け、CSS を p[_ngcontent-ng-c2462001973] { color: rgb(255, 0, 0); } に書き換えて head に入れているためです。ホスト要素には _nghost-ng-c... が付き、:host は [_nghost-ng-c503633429] に書き換えられていました。
  • None:下線はページ中のすべての p に効きました。p { text-decoration: underline; } がそのまま全体のスタイルとして入るためです。今回は ShadowDom のコンポーネントの中にも届いていました(シャドウルートの中に、アプリの各コンポーネントのスタイルが5つ入っていることを確認しました)。
  • ShadowDom:太字は自分の p だけに効きました。p はシャドウツリーの中に作られ、外から querySelector('app-enc-shadow p') で探しても見つかりません(結果は null)。
モード 仕組み スタイルが外へ出るか 選ぶ場面
Emulated(既定) 属性を付けてセレクターを書き換える 出ない 通常はこれ
None 何もしない(全体のスタイルになる) 出る 全体に効かせたいスタイルを部品側に置くとき
ShadowDom ブラウザーの Shadow DOM を使う 出ない Web 標準の仕組みで区切りたいとき

公式ドキュメント(Styling components)は、Emulated について「コンポーネントの外で定義したグローバルなスタイルは、Emulated の内側の要素にも影響しうる」と説明しています。今回、None の下線が Emulated の p にも付いたのはこのためです。同じページには、より厳密に区切る ExperimentalIsolatedShadowDom というモードと、カプセル化を越えるための ::ng-deep は新しく使うことを強く勧めない、という説明も載っています。

hostプロパティと属性セレクターで既存の要素を拡張する

セレクターはタグ名だけでなく、属性でも書けます。公式ドキュメント(Component selectors)によると、使えるのはタグ名・属性・クラスのセレクターと :not、それらの連結とカンマ区切りで、子孫結合子などは使えません。接頭辞に ng は使わないことも案内されています。

次の例は、appPrimary 属性を付けた button 要素だけをコンポーネントにします。host プロパティで、ホスト要素(button 自身)にクラス・属性・クリック処理を割り当てています。

// src/app/primary-button.ts
import { Component, input, signal } from '@angular/core';

@Component({
  selector: 'button[appPrimary]',
  template: `<ng-content /> <small>({{ count() }})</small>`,
  styles: `
    :host {
      background: rgb(25, 118, 210);
      color: rgb(255, 255, 255);
    }
    :host(.pressed) {
      background: rgb(13, 71, 161);
    }
  `,
  host: {
    '[class.pressed]': 'count() > 0',
    '[attr.aria-label]': 'label()',
    '(click)': 'count.update(n => n + 1)',
  },
})
export class PrimaryButton {
  readonly label = input('送信');
  protected readonly count = signal(0);
}
// src/app/app.ts
import { Component } from '@angular/core';
import { PrimaryButton } from './primary-button';

@Component({
  imports: [PrimaryButton],
  selector: 'app-root',
  template: `
    <button appPrimary label="注文を送信">送信する</button>
    <button>ふつうのボタン</button>
  `,
})
export class App {}

実行結果です。

クリック前
  1つ目: 送信する(0)  class=""         aria-label="注文を送信"  背景 rgb(25, 118, 210)
  2つ目: ふつうのボタン  class=""       aria-label なし         背景 rgb(239, 239, 239)
クリック後
  1つ目: 送信する(1)  class="pressed"  aria-label="注文を送信"  背景 rgb(13, 71, 161)
  2つ目: ふつうのボタン  class=""       aria-label なし         背景 rgb(239, 239, 239)
host のキー 働き
'[class.pressed]': 'count() > 0' 式が真のときホスト要素に pressed クラスを付ける
'[attr.aria-label]': 'label()' ホスト要素の属性に値を入れる
'(click)': '...' ホスト要素のイベントを受け取る

<ng-content /> は、タグの間に書かれた中身(ここでは「送信する」)を差し込む場所です。属性セレクターにすると button 本来の機能(フォーカスやキーボード操作)をそのまま使えるので、ボタンやリンクを拡張するときに向いています。

入力があるコンポーネントのテストを書く

input.required を持つコンポーネントを、入力を渡さずにテストで作ると次のエラーになりました。必須の入力に値が入っていないためです。

 FAIL   shop-app  src/app/user-card/user-card.spec.ts > UserCard > 名前とランクを表示する
Error: NG0950: Input "name" is required but no value is available yet. Find more at https://v22.angular.dev/errors/NG0950

fixture.componentRef.setInput() で入力を渡すと通ります。完成形は次のとおりです。

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserCard } from './user-card';

describe('UserCard', () => {
  let fixture: ComponentFixture<UserCard>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserCard],
    }).compileComponents();

    fixture = TestBed.createComponent(UserCard);
    fixture.componentRef.setInput('name', '佐藤');
    fixture.componentRef.setInput('points', 120);
    await fixture.whenStable();
  });

  it('名前とランクを表示する', () => {
    const el = fixture.nativeElement as HTMLElement;
    expect(el.querySelector('h2')?.textContent).toBe('佐藤');
    expect(el.querySelector('p')?.textContent).toContain('ゴールド');
  });

  it('ボタンで selected が発火する', () => {
    let picked = '';
    fixture.componentInstance.selected.subscribe((v) => (picked = v));
    (fixture.nativeElement as HTMLElement).querySelector('button')!.click();
    expect(picked).toBe('佐藤');
  });
});
ng test --watch=false
 Test Files  8 passed (8)
      Tests  10 passed (10)

(プロジェクト内のほかの生成済みテストも含めた件数です。)

コンポーネントを作るときのチェックリスト

確認すること 見る場所
使う側の imports に入れたか 親コンポーネントの @Component
タグ名が selector と一致しているか 親のテンプレートと子の selector
input.required の値を渡しているか 親のテンプレート
[名前] の入力名が input() のプロパティ名と一致しているか 親のテンプレートと子のクラス
パイプやディレクティブも imports に入れたか 親コンポーネントの @Component
ホスト要素に display を指定したか 子の CSS の :host
スタイルを全体に効かせる必要が本当にあるか encapsulation(既定の Emulated のままでよいか)
テストで必須入力を setInput() しているか .spec.ts

よくある質問

Q. standalone: true は書いたほうがよいですか? A. 書かなくて構いません。22.2.1 の ng g c は standalone を書かずに生成し、そのまま imports に入れて使えました。公式 API リファレンスでも既定値は true です。standalone: false を書くのは、NgModule の declarations に入れる場合だけです。

Q. imports と TypeScript の import 文は何が違いますか? A. import 文は「このファイルでそのクラス名を使えるようにする」TypeScript の機能、imports 配列は「このテンプレートでその部品を使えるようにする」Angular の設定です。両方が必要で、imports 配列に入れ忘れると NG8001 になります。

Q. 生成されたコンポーネントに changeDetection が書かれていませんが、どの方式になりますか? A. 公式 API リファレンスでは changeDetection の既定が OnPush とされています。ng g c 名前 --change-detection Eager で生成すると、changeDetection: ChangeDetectionStrategy.Eager が明記された形になりました。状態を signal() や input() で持てば、OnPush のままで画面が更新されます(この記事の例はすべて既定のままです)。

Q. styleUrl と styleUrls はどちらを使いますか? A. CSS ファイルが1つなら styleUrl(文字列)、複数なら styleUrls(配列)です。ng g c は styleUrl で生成します。

まとめ

  • コンポーネント= @Component を付けたクラス+テンプレート+スタイル。 ng g c 名前 で4ファイルが生成されます。
  • standalone が既定なので、standalone: true は書かずに、使う側の imports に入れるだけで使えます。
  • 「NG8001: is not a known element」「NG8002」が出たら、まず imports を確認します。属性セレクターのディレクティブは入れ忘れてもエラーにならない点に注意します。
  • インライン(template・styles)と外部ファイル(templateUrl・styleUrl)は動作が同じ。部品の大きさで選びます。
  • :host でホスト要素を装飾し、最初に display を決めておくとレイアウトが安定します。
  • カプセル化は既定の Emulated が基本。 None にしたスタイルはページ全体に効くことを、計算後のスタイルで確認しました。
  • 必須入力のあるコンポーネントのテストは setInput() で値を渡します。

参考・出典

  • Angular 公式 API リファレンス「Component」(angular.dev/api/core/Component)
  • Angular 公式ドキュメント「Styling components」(angular.dev/guide/components/styling)
  • Angular 公式ドキュメント「Component selectors」(angular.dev/guide/components/selectors)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Angularコンポーネントの作り方|standaloneとimportsの基本」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?