| name | angular-core |
| description | Angular core patterns: standalone components, signals, inject, control flow, zoneless. Trigger: When creating Angular components, using signals, or setting up zoneless.
|
| metadata | {"author":"gentleman-programming","version":"1.0"} |
Template File (REQUIRED for production code)
NEVER inline the template in the TypeScript class. Always use a separate .html file.
@Component({
selector: 'app-user',
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './user.component.html',
})
export class UserComponent {}
<h2>{{ displayName() }}</h2>
@Component({
selector: 'app-user',
template: `<h2>{{ displayName() }}</h2>`
})
export class UserComponent {}
This is non-negotiable. Template logic belongs in its own file, not mixed with the component class.
Scope: This rule applies to production application code. Inline templates in documentation snippets and skill example code are acceptable for brevity and readability.
Standalone Components (REQUIRED)
Input/Output Functions (REQUIRED)
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>();
Signals for State (REQUIRED)
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()));
Prefer Signals Over Reactive Lifecycle Hooks
Use signals, effect(), and computed() for reactive component logic. ngOnInit() remains valid for imperative initialization, and ngOnDestroy() remains valid for cleanup. Avoid ngAfterViewInit() and ngOnChanges() when signals can express the same behavior more clearly.
ngOnInit() {
this.loadInitialUser();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['userId']) {
this.loadUser(changes['userId'].currentValue);
}
}
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);
private readonly userEffect = effect(() => {
this.loadUser(this.userId());
});
readonly displayName = computed(() => this.user()?.name ?? 'Guest');
When to Use What
| Need | Use |
|---|
| Imperative initialization | ngOnInit() |
| React to input changes | effect() watching the input signal |
| Derived/computed state | computed() |
| Side effects (API calls, localStorage) | effect() |
| Cleanup on destroy | ngOnDestroy() or DestroyRef + inject() |
private readonly destroyRef = inject(DestroyRef);
constructor() {
const subscription = someObservable$.subscribe();
this.destroyRef.onDestroy(() => subscription.unsubscribe());
}
inject() Over Constructor (REQUIRED)
private readonly http = inject(HttpClient);
constructor(private http: HttpClient) {}
Native Control Flow (REQUIRED)
@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 - Only When Needed
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 |
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);
}
readonly searchResults$ = this.searchTerm$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.http.get<Results>(`/api/search?q=${term}`))
);
readonly searchResults = toSignal(this.searchResults$, { initialValue: [] });
Zoneless Angular (REQUIRED)
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.
Zoneless Requirements
- Use
OnPush change detection
- Use signals for state (auto-notifies Angular)
- Use
AsyncPipe for observables
- Use
markForCheck() when needed
Resources