jpskill.com
🛠️ 開発・MCP コミュニティ

web-framework-angular-standalone

Angular 17-19のスタンドアロンコンポーネント、シグナル、制御フロー、依存性注入パターンといった機能を活用し、より効率的でモダンなWebアプリケーション開発を支援するSkill。

📜 元の英語説明(参考)

Angular 17-19 standalone components, signals, control flow, dependency injection patterns

🇯🇵 日本人クリエイター向け解説

一言でいうと

Angular 17-19のスタンドアロンコンポーネント、シグナル、制御フロー、依存性注入パターンといった機能を活用し、より効率的でモダンなWebアプリケーション開発を支援するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o web-framework-angular-standalone.zip https://jpskill.com/download/10282.zip && unzip -o web-framework-angular-standalone.zip && rm web-framework-angular-standalone.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10282.zip -OutFile "$d\web-framework-angular-standalone.zip"; Expand-Archive "$d\web-framework-angular-standalone.zip" -DestinationPath $d -Force; ri "$d\web-framework-angular-standalone.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して web-framework-angular-standalone.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → web-framework-angular-standalone フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Angular スタンドアロンコンポーネント

クイックガイド: Angular 19 では、コンポーネントはデフォルトでスタンドアロンです。リアクティブな状態には signal()computed()effect()linkedSignal() を使用します。コンポーネント間の通信には input()output()model() を使用します。テンプレートの制御フローには @if@for@switch@defer を使用します。依存性注入には inject() を使用します。非同期データフェッチには resource() を使用します。


<critical_requirements>

重要: この Skill を使用する前に

すべてのコードは CLAUDE.md のプロジェクト規約に従う必要があります (kebab-case、名前付きエクスポート、インポート順序、import type、名前付き定数)

(スタンドアロンコンポーネントを記述する必要があります (Angular 19 のデフォルト) - standalone: false は、意図的に NgModule を使用する場合にのみ指定してください)

( @Input()@Output() デコレーターの代わりに、input()output()model() 関数を使用する必要があります)

(コンストラクタインジェクションではなく、依存性注入には inject() 関数を使用する必要があります)

*( `ngIfngForngSwitchではなく、@if@for@switch` 制御フローブロックを使用する必要があります)**

(すべての @for ループで track 式を使用する必要があります)

(依存する書き込み可能な状態には、手動でのシグナル同期の代わりに linkedSignal() を使用する必要があります)

</critical_requirements>


自動検出: Angular component, standalone component, signal, computed, effect, linkedSignal, resource, rxResource, httpResource, input(), output(), model(), @if, @for, @switch, @defer, inject(), provideRouter, afterRenderEffect

使用する場面:

  • スタンドアロンアーキテクチャで Angular 17-19 コンポーネントを構築する場合
  • シグナルを使用してリアクティブな状態を実装する場合
  • シグナルベースの入力/出力を使用してコンポーネント間の通信を作成する場合
  • スタンドアロンコンポーネントでルーティングを設定する場合
  • @defer または loadComponent でコンポーネントを遅延ロードする場合
  • resource()rxResource()、または httpResource() で非同期データをフェッチする場合

カバーする主要なパターン:

  • スタンドアロンコンポーネントアーキテクチャ (Angular 19 のデフォルト)
  • リアクティブな状態のためのシグナル (signal, computed, effect, linkedSignal)
  • 非同期データのための Resource API (resource, rxResource, httpResource) [実験的]
  • シグナルベースの入力と出力 (input, output, model)
  • 制御フローブロック (@if, @for, @switch, @defer)
  • inject() による依存性注入
  • provideRouter と loadComponent によるルーティング
  • afterRenderEffect() による DOM エフェクト

使用しない場面:

  • NgModule を使用する必要があるレガシー Angular プロジェクト (移行ガイドを参照)
  • Angular フレームワークを使用しない単純なスクリプト

詳細なリソース:

  • コアコードの例については、examples/core.md を参照してください
  • 高度なパターン (@defer, DI config, model(), RxJS interop) については、examples/ を参照してください
  • 意思決定フレームワークとアンチパターンについては、reference.md を参照してください

<philosophy>

Philosophy

Angular 17-19 は、NgModule のボイラープレートを排除するスタンドアロンファーストのアーキテクチャを採用しています。Angular 19 では、standalone: true がデフォルトです - NgModule コンポーネントの場合のみ standalone: false を指定する必要があります。シグナルは、予測可能な状態管理のために、同期的な、きめ細かいリアクティビティを提供します。新しい制御フロー構文 (@if@for@switch@defer) は、インポートなしでテンプレートに組み込まれており、より優れた型絞り込みとより小さなバンドルを提供します。コンポーネントは、独自の依存関係を宣言する、自己完結型の遅延ロード可能なユニットである必要があります。

Angular の 4 つの柱 (17-19):

  1. デフォルトでスタンドアロン - コンポーネント、ディレクティブ、およびパイプは、v19 ではデフォルトでスタンドアロンです
  2. シグナルベースのリアクティビティ - signal()computed()linkedSignal() を使用した、同期的な、メモ化された、きめ細かい変更検出
  3. 組み込みの制御フロー - インポートを必要とせず、ビルド時に最適化されるテンプレート構文
  4. Resource API - シグナルと統合された実験的な非同期データフェッチ (resource()rxResource()httpResource() in 19.2)

</philosophy>


<patterns>

コアパターン

パターン 1: スタンドアロンコンポーネントの構造

すべての Angular 17-19 コンポーネントは standalone: true (Angular 19 のデフォルト) を使用し、独自のインポートを宣言します。

// user-card.component.ts
import { Component, input, output } from "@angular/core";
import { DatePipe } from "@angular/common";

export type User = {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
};

@Component({
  selector: "app-user-card",
  standalone: true,
  imports: [DatePipe],
  template: `
    <article class="user-card">
      <h2>{{ user().name }}</h2>
      <p>{{ user().email }}</p>
      <time>Joined: {{ user().createdAt | date: "mediumDate" }}</time>
      <button (click)="edit.emit(user())">Edit</button>
    </article>
  `,
})
export class UserCardComponent {
  // シグナルベースの入力 (必須)
  user = input.required<User>();

  // シグナルベースの出力
  edit = output<User>();
}

良い点: standalone: true は NgModule のボイラープレートを排除し、imports 配列はツリーシェイキングのために依存関係を明示的に宣言し、シグナルベースの input() と output() は型安全なリアクティブ通信を提供し、テンプレートは可読性のために配置されています

// BAD - レガシーパターン
@Component({
  selector: "app-user-card",
  template: `...`,
})
export class UserCardComponent {
  @Input() user!: User; // レガシーデコレーター
  @Output() edit = new EventEmitter<User>(); // レガシー EventEmitter
}

悪い点: @Input デコレーターにはシグナルのリアクティビティがなく、EventEmitter は output() よりも型安全性が低く、non-null assertion (!) は潜在的な undefined エラーを隠蔽し、imports 配列がないことは依存関係が明示的でないことを意味します


パターン 2: リアクティブな状態のためのシグナル

書き込み可能な状態には signal() を、派生値には computed() を、副作用には effect() を使用します。重要なルール: 常に .set() または .update() を使用してミューテーションを行います (値を直接ミューテートしないでください)。派生値には computed() を使用します (メソッドではありません)。そして、effect() は真の副作用 (ロギング、分析、localStorage) のために予約します。


// 書き込み可能なシグナル
count = signal(0);

// 計算されたシグナル (読み取り専用、メモ化、dep

(原文がここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Angular Standalone Components

Quick Guide: Components are standalone by default in Angular 19. Use signal(), computed(), effect(), linkedSignal() for reactive state. Use input(), output(), model() for component communication. Use @if, @for, @switch, @defer for template control flow. Use inject() for dependency injection. Use resource() for async data fetching.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST write standalone components (the default in Angular 19) - only specify standalone: false when intentionally using NgModules)

(You MUST use input(), output(), model() functions instead of @Input(), @Output() decorators)

(You MUST use inject() function for dependency injection, NOT constructor injection)

*(You MUST use @if, @for, @switch control flow blocks, NOT `ngIf,ngFor,ngSwitch`)**

(You MUST use track expression in ALL @for loops)

(You MUST use linkedSignal() instead of manual signal synchronization for dependent writable state)

</critical_requirements>


Auto-detection: Angular component, standalone component, signal, computed, effect, linkedSignal, resource, rxResource, httpResource, input(), output(), model(), @if, @for, @switch, @defer, inject(), provideRouter, afterRenderEffect

When to use:

  • Building Angular 17-19 components with standalone architecture
  • Implementing reactive state with signals
  • Creating component communication with signal-based inputs/outputs
  • Setting up routing with standalone components
  • Lazy loading components with @defer or loadComponent
  • Fetching async data with resource(), rxResource(), or httpResource()

Key patterns covered:

  • Standalone component architecture (default in Angular 19)
  • Signals for reactive state (signal, computed, effect, linkedSignal)
  • Resource API for async data (resource, rxResource, httpResource) [experimental]
  • Signal-based inputs and outputs (input, output, model)
  • Control flow blocks (@if, @for, @switch, @defer)
  • Dependency injection with inject()
  • Routing with provideRouter and loadComponent
  • DOM effects with afterRenderEffect()

When NOT to use:

  • Legacy Angular projects that must use NgModules (consult migration guides)
  • Simple scripts without Angular framework

Detailed Resources:

  • For core code examples, see examples/core.md
  • For advanced patterns (@defer, DI config, model(), RxJS interop), see examples/
  • For decision frameworks and anti-patterns, see reference.md

<philosophy>

Philosophy

Angular 17-19 embraces a standalone-first architecture that eliminates NgModule boilerplate. In Angular 19, standalone: true is the default - you only need to specify standalone: false for NgModule components. Signals provide synchronous, fine-grained reactivity for predictable state management. The new control flow syntax (@if, @for, @switch, @defer) is built into templates without imports, offering better type narrowing and smaller bundles. Components should be self-contained, lazy-loadable units that declare their own dependencies.

Angular's Four Pillars (17-19):

  1. Standalone by Default - Components, directives, and pipes are standalone by default in v19
  2. Signal-Based Reactivity - Synchronous, memoized, fine-grained change detection with signal(), computed(), linkedSignal()
  3. Built-In Control Flow - Template syntax that requires no imports and optimizes at build time
  4. Resource API - Experimental async data fetching that integrates with signals (resource(), rxResource(), httpResource() in 19.2)

</philosophy>


<patterns>

Core Patterns

Pattern 1: Standalone Component Structure

All Angular 17-19 components use standalone: true (the default in Angular 19) and declare their own imports.

// user-card.component.ts
import { Component, input, output } from "@angular/core";
import { DatePipe } from "@angular/common";

export type User = {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
};

@Component({
  selector: "app-user-card",
  standalone: true,
  imports: [DatePipe],
  template: `
    <article class="user-card">
      <h2>{{ user().name }}</h2>
      <p>{{ user().email }}</p>
      <time>Joined: {{ user().createdAt | date: "mediumDate" }}</time>
      <button (click)="edit.emit(user())">Edit</button>
    </article>
  `,
})
export class UserCardComponent {
  // Signal-based input (required)
  user = input.required<User>();

  // Signal-based output
  edit = output<User>();
}

Why good: standalone: true eliminates NgModule boilerplate, imports array declares dependencies explicitly for tree-shaking, signal-based input() and output() provide type-safe reactive communication, template is colocated for readability

// BAD - Legacy patterns
@Component({
  selector: "app-user-card",
  template: `...`,
})
export class UserCardComponent {
  @Input() user!: User; // Legacy decorator
  @Output() edit = new EventEmitter<User>(); // Legacy EventEmitter
}

Why bad: @Input decorator lacks signal reactivity, EventEmitter is less type-safe than output(), non-null assertion (!) hides potential undefined errors, no imports array means dependencies aren't explicit


Pattern 2: Signals for Reactive State

Use signal() for writable state, computed() for derived values, and effect() for side effects. Key rules: always use .set() or .update() for mutations (never mutate the value directly), use computed() for derived values (not methods), and reserve effect() for true side effects (logging, analytics, localStorage).

// Writable signal
count = signal(0);

// Computed signal (read-only, memoized, recalculates only when deps change)
doubleCount = computed(() => this.count() * 2);

// Updating signals - always immutable
this.count.set(5); // Replace value
this.count.update((value) => value + 1); // Update from previous

// For arrays/objects: return new references
items = signal<Item[]>([]);
this.items.update((items) => [...items, newItem]); // Spread, don't push

// Effect for side effects only (not derived state)
effect(() => console.log(`Count: ${this.count()}`));

See examples/core.md for a full shopping cart example with signals.

// BAD - Direct mutation doesn't trigger reactivity
this.items().push(newItem);              // signal won't notify consumers
this.items.update(items => { items.push(newItem); return items; }); // same reference, no update

// BAD - Method instead of computed (recalculates every call, not memoized)
getTotal(): number { return this.items().reduce(...); }

Why bad: direct mutation doesn't trigger change detection, returning same reference skips equality check, methods lack memoization that computed() provides


Pattern 3: Signal Inputs and Outputs

Use input(), output(), and model() functions for component communication.

// search-input.component.ts
import { Component, input, output, model, computed } from "@angular/core";

const MIN_SEARCH_LENGTH = 3;

@Component({
  selector: "app-search-input",
  standalone: true,
  template: `
    <div class="search-input">
      <input
        [value]="query()"
        (input)="onInput($event)"
        [placeholder]="placeholder()"
      />
      @if (isValidSearch()) {
        <button (click)="search.emit(query())">Search</button>
      }
      @if (query()) {
        <button (click)="clear()">Clear</button>
      }
    </div>
  `,
})
export class SearchInputComponent {
  // Optional input with default value
  placeholder = input("Search...");

  // Required input
  minLength = input.required<number>();

  // Two-way binding with model()
  query = model("");

  // Output event
  search = output<string>();

  // Computed from inputs
  isValidSearch = computed(() => this.query().length >= this.minLength());

  onInput(event: Event): void {
    const target = event.target as HTMLInputElement;
    this.query.set(target.value);
  }

  clear(): void {
    this.query.set("");
  }
}

Usage in parent:

<app-search-input
  [minLength]="3"
  [(query)]="searchQuery"
  (search)="onSearch($event)"
/>

Why good: input() and input.required() clearly distinguish optional vs required props, model() enables two-way binding with [(query)] syntax, computed() derives validation state reactively, output() provides type-safe event emission


Pattern 4: Control Flow with @if, @for, @switch

Use built-in control flow blocks instead of structural directives.

// user-list.component.ts
import { Component, input, output } from "@angular/core";
import type { User } from "./user.types";

type LoadingState = "idle" | "loading" | "error" | "success";

@Component({
  selector: "app-user-list",
  standalone: true,
  template: `
    @switch (state()) {
      @case ("loading") {
        <div class="loading">Loading users...</div>
      }
      @case ("error") {
        <div class="error">
          <p>Failed to load users</p>
          <button (click)="retry.emit()">Retry</button>
        </div>
      }
      @case ("success") {
        @if (users().length > 0) {
          <ul class="user-list">
            @for (
              user of users();
              track user.id;
              let i = $index, first = $first, last = $last
            ) {
              <li [class.first]="first" [class.last]="last">
                <span class="index">{{ i + 1 }}.</span>
                <span class="name">{{ user.name }}</span>
                <span class="email">{{ user.email }}</span>
              </li>
            } @empty {
              <li class="empty">No users found</li>
            }
          </ul>
        } @else {
          <p>No users available</p>
        }
      }
      @default {
        <p>Ready to load users</p>
      }
    }
  `,
})
export class UserListComponent {
  users = input.required<User[]>();
  state = input<LoadingState>("idle");
  retry = output<void>();
}

Why good: @switch provides clear multi-branch logic, @for with track enables efficient DOM updates, @empty handles empty collections elegantly, $index/$first/$last provide iteration context without extra code, no CommonModule import required

// BAD - Legacy structural directives
@Component({
  imports: [CommonModule], // Extra import needed
  template: `
    <div *ngIf="loading; else content">Loading...</div>
    <ng-template #content>
      <ul>
        <li *ngFor="let user of users; trackBy: trackByFn; let i = index">
          {{ user.name }}
        </li>
      </ul>
    </ng-template>
  `,
})
export class UserListComponent {
  trackByFn(index: number, user: User): string {
    return user.id; // Separate function needed
  }
}

Why bad: requires CommonModule import, trackBy requires separate function, ng-template syntax is verbose, less optimal type narrowing


Pattern 5: Deferred Loading with @defer

Use @defer for lazy loading components and improving initial bundle size.

// dashboard.component.ts
import { Component, signal } from "@angular/core";

@Component({
  selector: "app-dashboard",
  standalone: true,
  template: `
    <h1>Dashboard</h1>

    <!-- Defer loading until viewport -->
    @defer (on viewport) {
      <app-heavy-chart />
    } @placeholder (minimum 200ms) {
      <div class="chart-skeleton">Chart loading...</div>
    } @loading (after 100ms; minimum 500ms) {
      <div class="spinner">Loading chart...</div>
    } @error {
      <div class="error">Failed to load chart</div>
    }

    <!-- Defer loading on interaction -->
    @defer (on interaction) {
      <app-comments-section />
    } @placeholder {
      <button>Load Comments</button>
    }

    <!-- Defer with condition -->
    @defer (when showAdvanced()) {
      <app-advanced-settings />
    } @placeholder {
      <p>Advanced settings will load when enabled</p>
    }

    <!-- Prefetch for faster navigation -->
    @defer (on idle; prefetch on hover) {
      <app-related-items />
    } @placeholder {
      <div class="related-skeleton">Related items</div>
    }
  `,
})
export class DashboardComponent {
  showAdvanced = signal(false);
}

Why good: @defer reduces initial bundle size by lazy-loading components, @placeholder prevents layout shift during load, @loading shows progress after delay to avoid flicker, @error handles failures gracefully, prefetch optimizes perceived performance

When to use @defer:

  • Heavy components below the fold (charts, data tables)
  • Features triggered by user interaction (comments, modals)
  • Conditional features that may never be needed
  • Components that can be prefetched on idle/hover

When NOT to use @defer:

  • Components visible on initial load (above the fold)
  • Critical UI that users need immediately
  • Components that would cause layout shift when loaded

Pattern 6: Dependency Injection with inject()

Use inject() function instead of constructor injection for cleaner, more flexible DI.

// user.service.ts
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import type { User } from "./user.types";

const API_BASE_URL = "/api";

@Injectable({ providedIn: "root" })
export class UserService {
  private http = inject(HttpClient);

  getUsers() {
    return this.http.get<User[]>(`${API_BASE_URL}/users`);
  }

  getUser(id: string) {
    return this.http.get<User>(`${API_BASE_URL}/users/${id}`);
  }
}
// user-profile.component.ts
import { Component, inject, resource } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { toSignal } from "@angular/core/rxjs-interop";
import type { User } from "./user.types";

const API_BASE_URL = "/api";

@Component({
  selector: "app-user-profile",
  standalone: true,
  template: `
    @if (userResource.isLoading()) {
      <p>Loading user...</p>
    }
    @if (userResource.hasValue()) {
      <h1>{{ userResource.value().name }}</h1>
      <p>{{ userResource.value().email }}</p>
    }
    @if (userResource.error(); as error) {
      <p>Error: {{ error }}</p>
      <button (click)="userResource.reload()">Retry</button>
    }
  `,
})
export class UserProfileComponent {
  private route = inject(ActivatedRoute);

  // Convert route params to signal
  private params = toSignal(this.route.params, { initialValue: { id: "" } });

  // resource() auto-refetches when userId changes
  userResource = resource({
    params: () => ({ id: this.params()["id"] }),
    loader: async ({ params, abortSignal }) => {
      const response = await fetch(`${API_BASE_URL}/users/${params.id}`, {
        signal: abortSignal,
      });
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response.json() as Promise<User>;
    },
  });
}

Why good: inject() provides cleaner syntax without constructor boilerplate, resource() handles loading/error states and race conditions automatically, no manual signal + effect combo needed

// BAD - Constructor injection (legacy)
export class UserProfileComponent {
  constructor(
    private route: ActivatedRoute,
    private userService: UserService,
  ) {}
}

Why bad: constructor injection requires boilerplate, doesn't work in field initializers, less flexible for conditional injection

inject() with options:

// Optional injection
private optionalService = inject(OptionalService, { optional: true });

// Skip self (look in parent injectors)
private parentService = inject(ParentService, { skipSelf: true });

// Self only (don't look in parent injectors)
private selfService = inject(SelfService, { self: true });

Pattern 7: Routing with Standalone Components

Configure routing using provideRouter and lazy load with loadComponent.

// app.config.ts
import { ApplicationConfig } from "@angular/core";
import {
  provideRouter,
  withComponentInputBinding,
  withPreloading,
  PreloadAllModules,
} from "@angular/router";
import { provideHttpClient } from "@angular/common/http";
import { routes } from "./app.routes";

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withComponentInputBinding(), // Bind route params to inputs
      withPreloading(PreloadAllModules), // Preload lazy routes
    ),
    provideHttpClient(),
  ],
};
// app.routes.ts
import type { Routes } from "@angular/router";

export const routes: Routes = [
  {
    path: "",
    loadComponent: () =>
      import("./home/home.component").then((m) => m.HomeComponent),
  },
  {
    path: "users",
    loadComponent: () =>
      import("./users/user-list.component").then((m) => m.UserListComponent),
  },
  {
    path: "users/:id",
    loadComponent: () =>
      import("./users/user-detail.component").then(
        (m) => m.UserDetailComponent,
      ),
  },
  {
    path: "admin",
    loadComponent: () =>
      import("./admin/admin.component").then((m) => m.AdminComponent),
    canActivate: [authGuard],
  },
  {
    path: "**",
    loadComponent: () =>
      import("./not-found/not-found.component").then(
        (m) => m.NotFoundComponent,
      ),
  },
];
// user-detail.component.ts - Using withComponentInputBinding
import { Component, input } from "@angular/core";

@Component({
  selector: "app-user-detail",
  standalone: true,
  template: `
    <h1>User {{ id() }}</h1>
    @if (tab()) {
      <p>Active tab: {{ tab() }}</p>
    }
  `,
})
export class UserDetailComponent {
  // Route param :id bound automatically with withComponentInputBinding
  id = input.required<string>();

  // Query param ?tab bound automatically
  tab = input<string | undefined>();
}

Why good: provideRouter replaces RouterModule.forRoot(), loadComponent lazy loads individual components without wrapper modules, withComponentInputBinding eliminates ActivatedRoute boilerplate, preloading improves navigation performance


Pattern 8: Lifecycle Hooks with Signals

Replace traditional lifecycle hooks with signal-based patterns.

// resize-observer.component.ts
import {
  Component,
  ElementRef,
  signal,
  inject,
  afterNextRender,
  afterRender,
  DestroyRef,
} from "@angular/core";

const DEBOUNCE_MS = 100;

@Component({
  selector: "app-resize-observer",
  standalone: true,
  template: `
    <div #container class="container">
      <p>Width: {{ width() }}px</p>
      <p>Height: {{ height() }}px</p>
    </div>
  `,
})
export class ResizeObserverComponent {
  private elementRef = inject(ElementRef);
  private destroyRef = inject(DestroyRef);

  width = signal(0);
  height = signal(0);

  constructor() {
    // Run once after first render (replaces ngAfterViewInit for DOM setup)
    afterNextRender(() => {
      this.setupResizeObserver();
    });

    // Run after every render (use sparingly)
    afterRender(() => {
      console.log("Component rendered");
    });
  }

  private setupResizeObserver(): void {
    const element = this.elementRef.nativeElement;
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        this.width.set(entry.contentRect.width);
        this.height.set(entry.contentRect.height);
      }
    });

    observer.observe(element);

    // Cleanup on destroy (replaces ngOnDestroy)
    this.destroyRef.onDestroy(() => {
      observer.disconnect();
    });
  }
}

Why good: afterNextRender runs after first render for DOM setup, afterRender provides per-render hooks, DestroyRef.onDestroy handles cleanup without implementing OnDestroy, signals automatically trigger change detection

Lifecycle hook mapping:

Legacy Hook Signal-Based Alternative
ngOnInit constructor + effect()
ngOnChanges effect() watching input() signals
ngAfterViewInit afterNextRender()
ngAfterViewChecked afterRender() (afterEveryRender() in v20+)
ngOnDestroy DestroyRef.onDestroy()
DOM side effects afterRenderEffect() with phases (v19+)

</patterns>


<integration>

Integration Guide

Angular standalone architecture is self-contained. Components declare their own imports and providers. Routing uses provideRouter. Services use providedIn: "root" or component-level providers.

Bootstrapping:

// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { AppComponent } from "./app/app.component";
import { appConfig } from "./app/app.config";

bootstrapApplication(AppComponent, appConfig).catch((err) =>
  console.error(err),
);

Component Communication:

  • Parent to child: input() and input.required()
  • Child to parent: output() with .emit()
  • Two-way binding: model() with [()] syntax
  • Across tree: Services with inject()

RxJS Interop:

import { toSignal, toObservable } from "@angular/core/rxjs-interop";

// Observable to Signal
const users = toSignal(this.userService.getUsers(), { initialValue: [] });

// Signal to Observable
const count$ = toObservable(this.count);

</integration>


<red_flags>

RED FLAGS

High Priority:

  • Using @Input/@Output decorators - Legacy pattern; use input(), output(), model() signal functions
  • Using ngIf/ngFor/*ngSwitch - Legacy directives; use @if, @for, @switch built-in control flow
  • Missing track in @for - Causes unnecessary DOM recreation and poor performance
  • Constructor injection instead of inject() - More boilerplate, less flexible
  • Mutating signal values directly - signal().push(item) doesn't trigger updates; use .update() with spread
  • Manual signal sync instead of linkedSignal() - Use linkedSignal() for writable derived state (v19+)
  • Using resource() for mutations - resource()/rxResource()/httpResource() are read-only; use HttpClient for POST/PUT/DELETE

Medium Priority:

  • @defer above the fold - Hurts LCP and CLS Core Web Vitals
  • effect() for derived state - Use computed() or linkedSignal() instead
  • effect() for DOM operations - Use afterRenderEffect() with phases
  • toSignal() without initialValue - Can cause runtime errors if observable hasn't emitted
  • Not checking resource hasValue() - Use hasValue() as type guard before accessing value()

Gotchas & Edge Cases:

  • signal() uses Object.is() equality by default; provide custom equality for objects
  • inject() must be called in constructor or field initializer, not in methods
  • @defer always renders @placeholder on server (SSR); triggers are ignored server-side
  • linkedSignal() value resets when source signal changes; use computation form to preserve previous
  • afterRenderEffect() without phase specification defaults to mixedReadWrite which can cause layout thrashing

See reference.md for complete decision frameworks, anti-patterns with code examples, and quick reference tables.

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md

(You MUST write standalone components (the default in Angular 19) - only specify standalone: false when intentionally using NgModules)

(You MUST use input(), output(), model() functions instead of @Input(), @Output() decorators)

(You MUST use inject() function for dependency injection, NOT constructor injection)

*(You MUST use @if, @for, @switch control flow blocks, NOT `ngIf,ngFor,ngSwitch`)**

(You MUST use track expression in ALL @for loops)

(You MUST use linkedSignal() instead of manual signal synchronization for dependent writable state)

Failure to follow these rules will produce legacy Angular code that misses performance optimizations and modern reactivity benefits.

</critical_reminders>