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

angular-20-standalone-component

Angular 20の最新パターンで、リアクティブな状態管理やフォーム処理、サービス連携が必要なUIコンポーネントを、簡潔なコードで効率的に作成・保守できる土台を提供するSkill。

📜 元の英語説明(参考)

Create Angular 20 standalone components using modern patterns: Signals for state management, input()/output() functions (not decorators), @if/@for/@switch control flow (not *ngIf/*ngFor), inject() dependency injection (not constructor), and OnPush change detection. Use this skill when scaffolding new UI components that need reactive state, form handling, or integration with services following the three-layer architecture.

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

一言でいうと

Angular 20の最新パターンで、リアクティブな状態管理やフォーム処理、サービス連携が必要なUIコンポーネントを、簡潔なコードで効率的に作成・保守できる土台を提供するSkill。

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

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

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

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

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

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して angular-20-standalone-component.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → angular-20-standalone-component フォルダができる
  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 20 スタンドアロンコンポーネントスキル

このスキルは、最新のパターンとプロジェクト標準に従って Angular 20 コンポーネントを作成するのに役立ちます。

コア原則

最新の Angular 20 パターン

  • スタンドアロンコンポーネント: 100% スタンドアロン、NgModule は不要
  • Signals: signal()computed()effect() を状態に使用
  • 新しい構文: input()output()@if@for@switch
  • inject(): 関数ベースの依存性注入
  • OnPush: パフォーマンス向上のための変更検知戦略

アーキテクチャ統合

  • プレゼンテーション層: コンポーネントは UI のみを処理
  • サービス統合: ビジネスロジックのためにサービスを注入
  • 直接リポジトリなし: リポジトリを直接注入しない
  • イベント駆動: モジュール間の通信には EventBus を使用

コンポーネントテンプレート

import { Component, signal, computed, effect, input, output, inject, ChangeDetectionStrategy } from '@angular/core';
import { SHARED_IMPORTS } from '@shared';
import { YourService } from '@core/services/your.service';

@Component({
  selector: 'app-your-component',
  standalone: true,
  imports: [SHARED_IMPORTS],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="component-container">
      @if (loading()) {
        <nz-spin nzSimple />
      } @else if (hasError()) {
        <nz-alert 
          nzType="error" 
          [nzMessage]="errorMessage()!"
          nzShowIcon
        />
      } @else {
        <div class="content">
          @for (item of items(); track item.id) {
            <app-item-card 
              [item]="item"
              (itemChange)="handleItemChange($event)"
            />
          } @empty {
            <nz-empty nzNotFoundContent="No items found" />
          }
        </div>
      }
    </div>
  `,
  styles: [`
    .component-container {
      padding: 24px;
    }

    .content {
      display: grid;
      gap: 16px;
    }
  `]
})
export class YourComponent {
  // ✅ inject() でサービスを注入
  private yourService = inject(YourService);

  // ✅ プロパティには input() を使用 (@Input() ではない)
  blueprintId = input.required<string>();
  readonly = input(false);

  // ✅ イベントには output() を使用 (@Output() ではない)
  itemChange = output<Item>();

  // ✅ 可変状態には signal() を使用
  loading = signal(false);
  error = signal<string | null>(null);
  items = signal<Item[]>([]);

  // ✅ 派生状態には computed() を使用
  hasError = computed(() => this.error() !== null);
  errorMessage = computed(() => this.error());
  totalItems = computed(() => this.items().length);

  // ✅ 副作用には effect() を使用
  constructor() {
    effect(() => {
      const id = this.blueprintId();
      console.log('Blueprint ID changed:', id);
      this.loadItems(id);
    });
  }

  ngOnInit(): void {
    this.loadItems(this.blueprintId());
  }

  async loadItems(blueprintId: string): Promise<void> {
    this.loading.set(true);
    this.error.set(null);

    try {
      const items = await this.yourService.getItems(blueprintId);
      this.items.set(items);
    } catch (err) {
      this.error.set(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      this.loading.set(false);
    }
  }

  handleItemChange(item: Item): void {
    // ローカル状態を更新
    this.items.update(items => 
      items.map(i => i.id === item.id ? item : i)
    );

    // 親に発行
    this.itemChange.emit(item);
  }
}

主要なパターン

1. Signal による状態管理

// 書き込み可能な signals
private _items = signal<Item[]>([]);

// 読み取り専用のパブリックアクセス
items = this._items.asReadonly();

// computed による派生状態
filteredItems = computed(() => 
  this._items().filter(item => item.status === 'active')
);

// signals の更新
this._items.set([...]); // 置換
this._items.update(items => [...items, newItem]); // 変換

2. 制御フロー構文

// ✅ 正しい: 新しい @if 構文
@if (condition()) {
  <div>Content</div>
} @else if (otherCondition()) {
  <div>Other</div>
} @else {
  <div>Default</div>
}

// ✅ 正しい: 新しい @for 構文 (track 付き)
@for (item of items(); track item.id) {
  <div>{{ item.name }}</div>
} @empty {
  <p>No items</p>
}

// ✅ 正しい: 新しい @switch 構文
@switch (status()) {
  @case ('active') { <span class="badge-success">Active</span> }
  @case ('inactive') { <span class="badge-danger">Inactive</span> }
  @default { <span class="badge-default">Unknown</span> }
}

// ❌ 間違い: 古い構文 (禁止)
<div *ngIf="condition">...</div>
<div *ngFor="let item of items">...</div>
<div [ngSwitch]="status">...</div>

3. Input/Output 関数

// ✅ 正しい: input()/output() 関数を使用
task = input.required<Task>();
readonly = input(false);
taskChange = output<Task>();

// ❌ 間違い: デコレーター (禁止)
@Input() task!: Task;
@Output() taskChange = new EventEmitter<Task>();

4. 依存性注入

// ✅ 正しい: inject() を使用
private taskService = inject(TaskService);
private router = inject(Router);
private destroyRef = inject(DestroyRef);

// ❌ 間違い: コンストラクタ注入 (禁止)
constructor(
  private taskService: TaskService,
  private router: Router
) {}

5. サブスクリプション

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

// ✅ 正しい: takeUntilDestroyed による自動クリーンアップ
private destroyRef = inject(DestroyRef);

ngOnInit(): void {
  this.service.data$
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe(data => this.items.set(data));
}

// ❌ 間違い: クリーンアップなしの手動サブスクリプション
ngOnInit(): void {
  this.service.data$.subscribe(data => this.items.set(data));
}

コンポーネントの種類

スマートコンポーネント (コンテナ)


@Component({
  selector: 'app-task-list',
  standalone: true,
  imports: [SHARED_IMPORTS, TaskItemComponent],
  template: `
    @for (task of tasks(); track task.id) {
      <app-task-item 
        [task]="task"
        (taskChange)="updateTask($event)"
      />
    }
  `
})

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

Angular 20 Standalone Component Skill

This skill helps create Angular 20 components following modern patterns and project standards.

Core Principles

Modern Angular 20 Patterns

  • Standalone Components: 100% standalone, zero NgModules
  • Signals: Use signal(), computed(), effect() for state
  • New Syntax: input(), output(), @if, @for, @switch
  • inject(): Function-based dependency injection
  • OnPush: Change detection strategy for performance

Architecture Integration

  • Presentation Layer: Components handle UI only
  • Service Integration: Inject services for business logic
  • No Direct Repository: Never inject repositories directly
  • Event-Driven: Use EventBus for cross-module communication

Component Template

import { Component, signal, computed, effect, input, output, inject, ChangeDetectionStrategy } from '@angular/core';
import { SHARED_IMPORTS } from '@shared';
import { YourService } from '@core/services/your.service';

@Component({
  selector: 'app-your-component',
  standalone: true,
  imports: [SHARED_IMPORTS],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="component-container">
      @if (loading()) {
        <nz-spin nzSimple />
      } @else if (hasError()) {
        <nz-alert 
          nzType="error" 
          [nzMessage]="errorMessage()!"
          nzShowIcon
        />
      } @else {
        <div class="content">
          @for (item of items(); track item.id) {
            <app-item-card 
              [item]="item"
              (itemChange)="handleItemChange($event)"
            />
          } @empty {
            <nz-empty nzNotFoundContent="No items found" />
          }
        </div>
      }
    </div>
  `,
  styles: [`
    .component-container {
      padding: 24px;
    }

    .content {
      display: grid;
      gap: 16px;
    }
  `]
})
export class YourComponent {
  // ✅ Inject services with inject()
  private yourService = inject(YourService);

  // ✅ Use input() for properties (NOT @Input())
  blueprintId = input.required<string>();
  readonly = input(false);

  // ✅ Use output() for events (NOT @Output())
  itemChange = output<Item>();

  // ✅ Use signal() for mutable state
  loading = signal(false);
  error = signal<string | null>(null);
  items = signal<Item[]>([]);

  // ✅ Use computed() for derived state
  hasError = computed(() => this.error() !== null);
  errorMessage = computed(() => this.error());
  totalItems = computed(() => this.items().length);

  // ✅ Use effect() for side effects
  constructor() {
    effect(() => {
      const id = this.blueprintId();
      console.log('Blueprint ID changed:', id);
      this.loadItems(id);
    });
  }

  ngOnInit(): void {
    this.loadItems(this.blueprintId());
  }

  async loadItems(blueprintId: string): Promise<void> {
    this.loading.set(true);
    this.error.set(null);

    try {
      const items = await this.yourService.getItems(blueprintId);
      this.items.set(items);
    } catch (err) {
      this.error.set(err instanceof Error ? err.message : 'Unknown error');
    } finally {
      this.loading.set(false);
    }
  }

  handleItemChange(item: Item): void {
    // Update local state
    this.items.update(items => 
      items.map(i => i.id === item.id ? item : i)
    );

    // Emit to parent
    this.itemChange.emit(item);
  }
}

Key Patterns

1. Signal State Management

// Writable signals
private _items = signal<Item[]>([]);

// Read-only public access
items = this._items.asReadonly();

// Computed derived state
filteredItems = computed(() => 
  this._items().filter(item => item.status === 'active')
);

// Update signals
this._items.set([...]); // Replace
this._items.update(items => [...items, newItem]); // Transform

2. Control Flow Syntax

// ✅ CORRECT: New @if syntax
@if (condition()) {
  <div>Content</div>
} @else if (otherCondition()) {
  <div>Other</div>
} @else {
  <div>Default</div>
}

// ✅ CORRECT: New @for syntax with track
@for (item of items(); track item.id) {
  <div>{{ item.name }}</div>
} @empty {
  <p>No items</p>
}

// ✅ CORRECT: New @switch syntax
@switch (status()) {
  @case ('active') { <span class="badge-success">Active</span> }
  @case ('inactive') { <span class="badge-danger">Inactive</span> }
  @default { <span class="badge-default">Unknown</span> }
}

// ❌ WRONG: Old syntax (forbidden)
<div *ngIf="condition">...</div>
<div *ngFor="let item of items">...</div>
<div [ngSwitch]="status">...</div>

3. Input/Output Functions

// ✅ CORRECT: Use input()/output() functions
task = input.required<Task>();
readonly = input(false);
taskChange = output<Task>();

// ❌ WRONG: Decorators (forbidden)
@Input() task!: Task;
@Output() taskChange = new EventEmitter<Task>();

4. Dependency Injection

// ✅ CORRECT: Use inject()
private taskService = inject(TaskService);
private router = inject(Router);
private destroyRef = inject(DestroyRef);

// ❌ WRONG: Constructor injection (forbidden)
constructor(
  private taskService: TaskService,
  private router: Router
) {}

5. Subscriptions

import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

// ✅ CORRECT: Auto-cleanup with takeUntilDestroyed
private destroyRef = inject(DestroyRef);

ngOnInit(): void {
  this.service.data$
    .pipe(takeUntilDestroyed(this.destroyRef))
    .subscribe(data => this.items.set(data));
}

// ❌ WRONG: Manual subscriptions without cleanup
ngOnInit(): void {
  this.service.data$.subscribe(data => this.items.set(data));
}

Component Types

Smart Component (Container)

@Component({
  selector: 'app-task-list',
  standalone: true,
  imports: [SHARED_IMPORTS, TaskItemComponent],
  template: `
    @for (task of tasks(); track task.id) {
      <app-task-item 
        [task]="task"
        (taskChange)="updateTask($event)"
      />
    }
  `
})
export class TaskListComponent {
  private taskService = inject(TaskService);
  tasks = signal<Task[]>([]);

  ngOnInit(): void {
    this.loadTasks();
  }

  async loadTasks(): Promise<void> {
    const tasks = await this.taskService.getTasks();
    this.tasks.set(tasks);
  }

  async updateTask(task: Task): Promise<void> {
    await this.taskService.updateTask(task.id, task);
    this.tasks.update(tasks => 
      tasks.map(t => t.id === task.id ? task : t)
    );
  }
}

Presentational Component (Pure)

@Component({
  selector: 'app-task-item',
  standalone: true,
  imports: [SHARED_IMPORTS],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <nz-card>
      <h3>{{ task().title }}</h3>
      <p>{{ task().description }}</p>
      <button nz-button (click)="handleComplete()">
        Complete
      </button>
    </nz-card>
  `
})
export class TaskItemComponent {
  task = input.required<Task>();
  taskChange = output<Task>();

  handleComplete(): void {
    const updated = { ...this.task(), status: 'completed' };
    this.taskChange.emit(updated);
  }
}

Form Handling

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-task-form',
  standalone: true,
  imports: [SHARED_IMPORTS, ReactiveFormsModule],
  template: `
    <form nz-form [formGroup]="form" (ngSubmit)="handleSubmit()">
      <nz-form-item>
        <nz-form-label nzRequired>Title</nz-form-label>
        <nz-form-control nzErrorTip="Please enter task title">
          <input nz-input formControlName="title" />
        </nz-form-control>
      </nz-form-item>

      <button nz-button nzType="primary" [disabled]="!form.valid">
        Submit
      </button>
    </form>
  `
})
export class TaskFormComponent {
  private fb = inject(FormBuilder);

  form = this.fb.group({
    title: ['', [Validators.required, Validators.maxLength(200)]],
    description: [''],
    status: ['pending']
  });

  taskSubmit = output<Partial<Task>>();

  handleSubmit(): void {
    if (this.form.valid) {
      this.taskSubmit.emit(this.form.value);
      this.form.reset();
    }
  }
}

Checklist

When creating a component:

  • [ ] Standalone component with imports
  • [ ] Uses signal() for state
  • [ ] Uses computed() for derived state
  • [ ] Uses input()/output() functions
  • [ ] Uses @if/@for/@switch syntax
  • [ ] Uses inject() for dependencies
  • [ ] OnPush change detection
  • [ ] No business logic in component
  • [ ] Proper error handling
  • [ ] Loading states
  • [ ] Empty states
  • [ ] TypeScript strict typing

References