angular-component
Angular v20以降のベストプラクティスに沿って、UIコンポーネントを構築し、シグナルベースの入出力やOnPush変更検知、ホストバインディングなどを活用したモダンなAngularスタンドアロンコンポーネントを効率的に作成・改善するSkill。
📜 元の英語説明(参考)
Create modern Angular standalone components following v20+ best practices. Use for building UI components with signal-based inputs/outputs, OnPush change detection, host bindings, content projection, and lifecycle hooks. Triggers on component creation, refactoring class-based inputs to signals, adding host bindings, or implementing accessible interactive components.
🇯🇵 日本人クリエイター向け解説
Angular v20以降のベストプラクティスに沿って、UIコンポーネントを構築し、シグナルベースの入出力やOnPush変更検知、ホストバインディングなどを活用したモダンなAngularスタンドアロンコンポーネントを効率的に作成・改善するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o angular-component.zip https://jpskill.com/download/17164.zip && unzip -o angular-component.zip && rm angular-component.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17164.zip -OutFile "$d\angular-component.zip"; Expand-Archive "$d\angular-component.zip" -DestinationPath $d -Force; ri "$d\angular-component.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
angular-component.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
angular-componentフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 2
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Angular コンポーネント
Angular v20+ 用のスタンドアロンコンポーネントを作成します。コンポーネントはデフォルトでスタンドアロンです。standalone: true を設定しないでください。
コンポーネントの構造
import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core';
@Component({
selector: 'app-user-card',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'class': 'user-card',
'[class.active]': 'isActive()',
'(click)': 'handleClick()',
},
template: `
<img [src]="avatarUrl()" [alt]="name() + ' avatar'" />
<h2>{{ name() }}</h2>
@if (showEmail()) {
<p>{{ email() }}</p>
}
`,
styles: `
:host { display: block; }
:host.active { border: 2px solid blue; }
`,
})
export class UserCard {
// 必須の入力
name = input.required<string>();
// デフォルト値を持つオプションの入力
email = input<string>('');
showEmail = input(false);
// 変換付きの入力
isActive = input(false, { transform: booleanAttribute });
// 入力から算出
avatarUrl = computed(() => `https://api.example.com/avatar/${this.name()}`);
// 出力
selected = output<string>();
handleClick() {
this.selected.emit(this.name());
}
}
シグナル入力
// 必須 - 親から提供される必要があります
name = input.required<string>();
// デフォルト値を持つオプション
count = input(0);
// デフォルト値なしのオプション (undefined が許可されます)
label = input<string>();
// テンプレートバインディングのエイリアス付き
size = input('medium', { alias: 'buttonSize' });
// 変換関数付き
disabled = input(false, { transform: booleanAttribute });
value = input(0, { transform: numberAttribute });
シグナル出力
import { output, outputFromObservable } from '@angular/core';
// 基本的な出力
clicked = output<void>();
selected = output<Item>();
// エイリアス付き
valueChange = output<number>({ alias: 'change' });
// Observable から (RxJS 相互運用のため)
scroll$ = new Subject<number>();
scrolled = outputFromObservable(this.scroll$);
// 値を発行
this.clicked.emit();
this.selected.emit(item);
ホストバインディング
@Component の host オブジェクトを使用してください。@HostBinding または @HostListener デコレーターは使用しないでください。
@Component({
selector: 'app-button',
host: {
// 静的属性
'role': 'button',
// 動的なクラスバインディング
'[class.primary]': 'variant() === "primary"',
'[class.disabled]': 'disabled()',
// 動的なスタイルバインディング
'[style.--btn-color]': 'color()',
// 属性バインディング
'[attr.aria-disabled]': 'disabled()',
'[attr.tabindex]': 'disabled() ? -1 : 0',
// イベントリスナー
'(click)': 'onClick($event)',
'(keydown.enter)': 'onClick($event)',
'(keydown.space)': 'onClick($event)',
},
template: `<ng-content />`,
})
export class Button {
variant = input<'primary' | 'secondary'>('primary');
disabled = input(false, { transform: booleanAttribute });
color = input('#007bff');
clicked = output<void>();
onClick(event: Event) {
if (!this.disabled()) {
this.clicked.emit();
}
}
}
コンテンツプロジェクション
@Component({
selector: 'app-card',
template: `
<header>
<ng-content select="[card-header]" />
</header>
<main>
<ng-content />
</main>
<footer>
<ng-content select="[card-footer]" />
</footer>
`,
})
export class Card {}
// 使用例:
// <app-card>
// <h2 card-header>Title</h2>
// <p>Main content</p>
// <button card-footer>Action</button>
// </app-card>
ライフサイクルフック
import { OnDestroy, OnInit, afterNextRender, afterRender } from '@angular/core';
export class My implements OnInit, OnDestroy {
constructor() {
// レンダリング後の DOM 操作用 (SSR セーフ)
afterNextRender(() => {
// 最初のレンダリング後に一度実行されます
});
afterRender(() => {
// すべてのレンダリング後に実行されます
});
}
ngOnInit() { /* コンポーネントが初期化されました */ }
ngOnDestroy() { /* クリーンアップ */ }
}
アクセシビリティ要件
コンポーネントは以下を満たす必要があります。
- AXE アクセシビリティチェックに合格する
- WCAG AA 基準を満たす
- インタラクティブな要素に適切な ARIA 属性を含める
- キーボードナビゲーションをサポートする
- 可視フォーカスインジケーターを維持する
@Component({
selector: 'app-toggle',
host: {
'role': 'switch',
'[attr.aria-checked]': 'checked()',
'[attr.aria-label]': 'label()',
'tabindex': '0',
'(click)': 'toggle()',
'(keydown.enter)': 'toggle()',
'(keydown.space)': 'toggle(); $event.preventDefault()',
},
template: `<span class="toggle-track"><span class="toggle-thumb"></span></span>`,
})
export class Toggle {
label = input.required<string>();
checked = input(false, { transform: booleanAttribute });
checkedChange = output<boolean>();
toggle() {
this.checkedChange.emit(!this.checked());
}
}
テンプレート構文
ネイティブの制御フローを使用してください。*ngIf、*ngFor、*ngSwitch は使用しないでください。
<!-- 条件 -->
@if (isLoading()) {
<app-spinner />
} @else if (error()) {
<app-error [message]="error()" />
} @else {
<app-content [data]="data()" />
}
<!-- ループ -->
@for (item of items(); track item.id) {
<app-item [item]="item" />
} @empty {
<p>No items found</p>
}
<!-- Switch -->
@switch (status()) {
@case ('pending') { <span>Pending</span> }
@case ('active') { <span>Active</span> }
@default { <span>Unknown</span> }
}
クラスとスタイルのバインディング
ngClass または ngStyle は使用しないでください。直接バインディングを使用してください。
<!-- クラスバインディング -->
<div [class.active]="isActive()">Single class</div>
<div [class]="classString()">Class string</div>
<!-- スタイルバインディング -->
<div [style.color]="textColor()">Styled text</div>
<div [style.width.px]="width()">With unit</div>
画像
静的画像には NgOptimizedImage を使用してください。
import { NgOptimizedImage } from '@angular/common';
@Component({
imports: [NgOptimizedImage],
template: `
<img ngSrc="/assets/hero.jpg" width="800" height=" 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Angular Component
Create standalone components for Angular v20+. Components are standalone by default—do NOT set standalone: true.
Component Structure
import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core';
@Component({
selector: 'app-user-card',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'class': 'user-card',
'[class.active]': 'isActive()',
'(click)': 'handleClick()',
},
template: `
<img [src]="avatarUrl()" [alt]="name() + ' avatar'" />
<h2>{{ name() }}</h2>
@if (showEmail()) {
<p>{{ email() }}</p>
}
`,
styles: `
:host { display: block; }
:host.active { border: 2px solid blue; }
`,
})
export class UserCard {
// Required input
name = input.required<string>();
// Optional input with default
email = input<string>('');
showEmail = input(false);
// Input with transform
isActive = input(false, { transform: booleanAttribute });
// Computed from inputs
avatarUrl = computed(() => `https://api.example.com/avatar/${this.name()}`);
// Output
selected = output<string>();
handleClick() {
this.selected.emit(this.name());
}
}
Signal Inputs
// Required - must be provided by parent
name = input.required<string>();
// Optional with default value
count = input(0);
// Optional without default (undefined allowed)
label = input<string>();
// With alias for template binding
size = input('medium', { alias: 'buttonSize' });
// With transform function
disabled = input(false, { transform: booleanAttribute });
value = input(0, { transform: numberAttribute });
Signal Outputs
import { output, outputFromObservable } from '@angular/core';
// Basic output
clicked = output<void>();
selected = output<Item>();
// With alias
valueChange = output<number>({ alias: 'change' });
// From Observable (for RxJS interop)
scroll$ = new Subject<number>();
scrolled = outputFromObservable(this.scroll$);
// Emit values
this.clicked.emit();
this.selected.emit(item);
Host Bindings
Use the host object in @Component—do NOT use @HostBinding or @HostListener decorators.
@Component({
selector: 'app-button',
host: {
// Static attributes
'role': 'button',
// Dynamic class bindings
'[class.primary]': 'variant() === "primary"',
'[class.disabled]': 'disabled()',
// Dynamic style bindings
'[style.--btn-color]': 'color()',
// Attribute bindings
'[attr.aria-disabled]': 'disabled()',
'[attr.tabindex]': 'disabled() ? -1 : 0',
// Event listeners
'(click)': 'onClick($event)',
'(keydown.enter)': 'onClick($event)',
'(keydown.space)': 'onClick($event)',
},
template: `<ng-content />`,
})
export class Button {
variant = input<'primary' | 'secondary'>('primary');
disabled = input(false, { transform: booleanAttribute });
color = input('#007bff');
clicked = output<void>();
onClick(event: Event) {
if (!this.disabled()) {
this.clicked.emit();
}
}
}
Content Projection
@Component({
selector: 'app-card',
template: `
<header>
<ng-content select="[card-header]" />
</header>
<main>
<ng-content />
</main>
<footer>
<ng-content select="[card-footer]" />
</footer>
`,
})
export class Card {}
// Usage:
// <app-card>
// <h2 card-header>Title</h2>
// <p>Main content</p>
// <button card-footer>Action</button>
// </app-card>
Lifecycle Hooks
import { OnDestroy, OnInit, afterNextRender, afterRender } from '@angular/core';
export class My implements OnInit, OnDestroy {
constructor() {
// For DOM manipulation after render (SSR-safe)
afterNextRender(() => {
// Runs once after first render
});
afterRender(() => {
// Runs after every render
});
}
ngOnInit() { /* Component initialized */ }
ngOnDestroy() { /* Cleanup */ }
}
Accessibility Requirements
Components MUST:
- Pass AXE accessibility checks
- Meet WCAG AA standards
- Include proper ARIA attributes for interactive elements
- Support keyboard navigation
- Maintain visible focus indicators
@Component({
selector: 'app-toggle',
host: {
'role': 'switch',
'[attr.aria-checked]': 'checked()',
'[attr.aria-label]': 'label()',
'tabindex': '0',
'(click)': 'toggle()',
'(keydown.enter)': 'toggle()',
'(keydown.space)': 'toggle(); $event.preventDefault()',
},
template: `<span class="toggle-track"><span class="toggle-thumb"></span></span>`,
})
export class Toggle {
label = input.required<string>();
checked = input(false, { transform: booleanAttribute });
checkedChange = output<boolean>();
toggle() {
this.checkedChange.emit(!this.checked());
}
}
Template Syntax
Use native control flow—do NOT use *ngIf, *ngFor, *ngSwitch.
<!-- Conditionals -->
@if (isLoading()) {
<app-spinner />
} @else if (error()) {
<app-error [message]="error()" />
} @else {
<app-content [data]="data()" />
}
<!-- Loops -->
@for (item of items(); track item.id) {
<app-item [item]="item" />
} @empty {
<p>No items found</p>
}
<!-- Switch -->
@switch (status()) {
@case ('pending') { <span>Pending</span> }
@case ('active') { <span>Active</span> }
@default { <span>Unknown</span> }
}
Class and Style Bindings
Do NOT use ngClass or ngStyle. Use direct bindings:
<!-- Class bindings -->
<div [class.active]="isActive()">Single class</div>
<div [class]="classString()">Class string</div>
<!-- Style bindings -->
<div [style.color]="textColor()">Styled text</div>
<div [style.width.px]="width()">With unit</div>
Images
Use NgOptimizedImage for static images:
import { NgOptimizedImage } from '@angular/common';
@Component({
imports: [NgOptimizedImage],
template: `
<img ngSrc="/assets/hero.jpg" width="800" height="600" priority />
<img [ngSrc]="imageUrl()" width="200" height="200" />
`,
})
export class Hero {
imageUrl = input.required<string>();
}
For detailed patterns, see references/component-patterns.md.
同梱ファイル
※ ZIPに含まれるファイル一覧。`SKILL.md` 本体に加え、参考資料・サンプル・スクリプトが入っている場合があります。
- 📄 SKILL.md (6,667 bytes)
- 📎 references/component-patterns.md (7,407 bytes)