angular-core by gentleman-programming/gentleman-skills
npx skills add https://github.com/gentleman-programming/gentleman-skills --skill angular-core组件默认是独立的。请勿设置 standalone: true。
@Component({
selector: 'app-user',
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class UserComponent {}
// ✅ 始终使用:基于函数的方式
readonly user = input.required<User>();
readonly disabled = input(false);
readonly selected = output<User>();
readonly checked = model(false); // 双向绑定
// ❌ 禁止使用:装饰器
@Input() user: User;
@Output() selected = new EventEmitter<User>();
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);
// 更新
this.count.set(5);
this.count.update(prev => prev + 1);
// 副作用
effect(() => localStorage.setItem('count', this.count().toString()));
Signal 已取代生命周期钩子。请勿使用 、、。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
ngOnInitngOnChangesngOnDestroy// ❌ 禁止使用:生命周期钩子
ngOnInit() {
this.loadUser();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['userId']) {
this.loadUser();
}
}
// ✅ 始终使用:Signal + effect
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);
private userEffect = effect(() => {
// 当 userId() 变化时自动运行
this.loadUser(this.userId());
});
// ✅ 对于派生数据,使用 computed
readonly displayName = computed(() => this.user()?.name ?? 'Guest');
| 需求 | 使用方式 |
|---|---|
| 响应输入变化 | 使用 effect() 监听输入 signal |
| 派生/计算状态 | 使用 computed() |
| 副作用(API 调用、localStorage) | 使用 effect() |
| 销毁时清理 | 使用 DestroyRef + inject() |
// 清理示例
private readonly destroyRef = inject(DestroyRef);
constructor() {
const subscription = someObservable$.subscribe();
this.destroyRef.onDestroy(() => subscription.unsubscribe());
}
inject() 而非构造函数(必需)// ✅ 始终使用
private readonly http = inject(HttpClient);
// ❌ 禁止使用
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> }
}
Signal 是默认选择。仅在处理复杂异步操作时使用 RxJS。
| 使用 Signal 的场景 | 使用 RxJS 的场景 |
|---|---|
| 组件状态 | 合并多个流 |
| 派生值 | 防抖/节流 |
| 简单异步(单次 API 调用) | 竞态条件 |
| 输入/输出 | WebSockets、实时数据 |
| 复杂的错误重试逻辑 |
// ✅ 简单的 API 调用 - 使用 signal
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);
}
// ✅ 复杂的流 - 使用 RxJS
readonly searchResults$ = this.searchTerm$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.http.get<Results>(`/api/search?q=${term}`))
);
// 当模板中需要时转换为 signal
readonly searchResults = toSignal(this.searchResults$, { initialValue: [] });
Angular 是无 Zone 的。请使用 provideZonelessChangeDetection()。
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()]
});
移除 ZoneJS:
npm uninstall zone.js
从 angular.json 的 polyfills 中移除:zone.js 和 zone.js/testing。
OnPush 变更检测策略AsyncPipemarkForCheck()每周安装量
81
代码仓库
GitHub 星标数
374
首次出现
2026年1月24日
安全审计
安装于
opencode69
gemini-cli61
codex61
github-copilot59
cursor58
kimi-cli56
Components are standalone by default. Do NOT set 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>();
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 replace lifecycle hooks. Do NOT use 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');
| Need | Use |
|---|---|
| React to input changes | effect() watching the input signal |
| Derived/computed state | computed() |
| Side effects (API calls, localStorage) | effect() |
| Cleanup on destroy | DestroyRef + inject() |
// Cleanup example
private readonly destroyRef = inject(DestroyRef);
constructor() {
const subscription = someObservable$.subscribe();
this.destroyRef.onDestroy(() => subscription.unsubscribe());
}
// ✅ 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> }
}
Signals are the default. Use RxJS ONLY for complex async operations.
| Use Signals | Use RxJS |
|---|---|
| Component state | Combining multiple streams |
| Derived values | Debounce/throttle |
| Simple async (single API call) | Race conditions |
| Input/Output | WebSockets, real-time |
| Complex error retry logic |
// ✅ 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 is zoneless. Use provideZonelessChangeDetection().
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()]
});
Remove ZoneJS:
npm uninstall zone.js
Remove from angular.json polyfills: zone.js and zone.js/testing.
OnPush change detectionAsyncPipe for observablesmarkForCheck() when neededWeekly Installs
81
Repository
GitHub Stars
374
First Seen
Jan 24, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode69
gemini-cli61
codex61
github-copilot59
cursor58
kimi-cli56
Genkit JS 开发指南:AI 应用构建、错误排查与最佳实践
7,000 周安装
AI加速器指南:GPU/TPU/NPU硬件优化与CUDA张量核心实战技巧
2 周安装
Node.js 许可证检查器 - 自动扫描 node_modules 生成开源许可证合规报告
2 周安装
YouTube视频表现追踪与优化工具 - 基于强化学习的内容策略分析
2 周安装
云API集成专家:安全高效整合Claude、GPT-4、Gemini API,实现多供应商故障转移
80 周安装
软件可测试性设计指南:测试金字塔、API模式与本地测试实践
2 周安装
Mock Generator:自动生成测试模拟对象、桩和夹具,支持Jest/Vitest/Pytest等框架
2 周安装