angular-core について
スタンドアロンコンポーネント、シグナル、関数形式の入出力、ゾーンレス変更検知に関するAngularスキルです。Angularのパターンを示し、必要な場合に限ってRxJSを使う方針を説明します。
主な機能
- 01スタンドアロンコンポーネントの規約
- 02関数形式の入力と出力
- 03シグナルによる状態と副作用の管理
- 04ライフサイクルフックの回避
- 05inject()による依存性注入
- 06ゾーンレス変更検知
活用シーン
- 01スタンドアロンAngularコンポーネントの構築
- 02シグナルによるコンポーネント状態の管理
- 03effectによるライフサイクルフックの置き換え
- 04ゾーンレスAngularアプリの設定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ gentleman-programming/ gentleman-skills --skill angular-coreインストール後、Claude Code で/angular-coreを入力して読み込まれたか確認します。
詳細
スタンドアロンコンポーネント(必須)
コンポーネントはデフォルトでスタンドアロンです。standalone: true を設定しないでください。
@Component({
selector: 'app-user',
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class UserComponent {}
入出力関数(必須)
// ✅ ALWAYS: Function-based
readonly user = input.required<User>();
readonly disabled = input(false);
readonly selected = output<User>();
readonly checked = model(false); // Two-way binding
// ❌ NEVER: Decorators
@Input() user: User;
@Output() selected = new EventEmitter<User>();
状態管理には Signals を使用(必須)
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);
// Update
this.count.set(5);
this.count.update(prev => prev + 1);
// Side effects
effect(() => localStorage.setItem('count', this.count().toString()));
ライフサイクルフックは使用しない(必須)
Signals はライフサイクルフックに置き換わります。ngOnInit、ngOnChanges、ngOnDestroy は使用しないでください。
// ❌ NEVER: Lifecycle hooks
ngOnInit() {
this.loadUser();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['userId']) {
this.loadUser();
}
}
// ✅ ALWAYS: Signals + effect
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);
private userEffect = effect(() => {
// Runs automatically when userId() changes
this.loadUser(this.userId());
});
// ✅ For derived data, use computed
readonly displayName = computed(() => this.user()?.name ?? 'Guest');
用途に応じた使い分け
| 必要な処理 | 使用するもの |
|---|---|
| 入力の変更への反応 | 入力シグナルを監視する effect() |
| 派生状態/計算状態 | computed() |
| 副作用(API 呼び出し、localStorage) | effect() |
| 破棄時のクリーンアップ | DestroyRef + inject() |
// Cleanup example
private readonly destroyRef = inject(DestroyRef);
constructor() {
const subscription = someObservable$.subscribe();
this.destroyRef.onDestroy(() => subscription.unsubscribe());
}
コンストラクターより inject()(必須)
// ✅ ALWAYS
private readonly http = inject(HttpClient);
// ❌ NEVER
constructor(private http: HttpClient) {}
ネイティブ制御フロー(必須)
@if (loading()) {
<spinner />
} @else {
@for (item of items(); track item.id) {
<item-card [data]="item" />
} @empty {
<p>No items</p>
}
}
@switch (status()) {
@case ('active') { <span>Active</span> }
@default { <span>Unknown</span> }
}
RxJS — 必要な場合にのみ使用
Signals がデフォルトです。複雑な非同期処理に限り、RxJS を使用してください。
| Signals を使用 | RxJS を使用 |
|---|---|
| コンポーネントの状態 | 複数のストリームの結合 |
| 派生値 | デバウンス/スロットリング |
| 単純な非同期処理(単一の API 呼び出し) | 競合状態 |
| 入出力 | WebSocket、リアルタイム処理 |
| 複雑なエラー再試行ロジック |
// ✅ Simple API call - use signals
readonly user = signal<User | null>(null);
readonly loading = signal(false);
async loadUser(id: string) {
this.loading.set(true);
this.user.set(await firstValueFrom(this.http.get<User>(`/api/users/${id}`)));
this.loading.set(false);
}
// ✅ Complex stream - use RxJS
readonly searchResults$ = this.searchTerm$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.http.get<Results>(`/api/search?q=${term}`))
);
// Convert to signal when needed in template
readonly searchResults = toSignal(this.searchResults$, { initialValue: [] });
ゾーンレス Angular(必須)
Angular はゾーンレスです。provideZonelessChangeDetection() を使用してください。
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()]
});
ZoneJS を削除します:
npm uninstall zone.js
angular.json の polyfills から zone.js と zone.js/testing を削除します。
ゾーンレスの要件
OnPush変更検出を使用する- 状態には Signals を使用する(Angular に自動通知されます)
- オブザーバブルには
AsyncPipeを使用する - 必要に応じて
markForCheck()を使用する
リソース
コメント
Markdown 対応