Check a page for accessibility
Review supplied markup or descriptions for practical accessibility issues and fixes.
4,739,149 AI resources indexedWorking toward the world’s largest AI resource index.
Angular core patterns: standalone components, signals, inject, control flow, zoneless. Trigger: When creating Angular components, using signals, or setting up zoneless.
---
name: angular-core-23f1f7c3c03e
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.
```typescript
// ✅ ALWAYS - External template
@Component({
selector: 'app-user',
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './user.component.html',
})
export class UserComponent {}
```
```html
<!-- user.component.html -->
<h2>{{ displayName() }}</h2>
```
```typescript
// ❌ NEVER - Inlined template
@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)
```typescript
// ✅ 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 for State (REQUIRED)
```typescript
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()));
```
---
## 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.
```typescript
// ✅ Valid: imperative initialization
ngOnInit() {
this.loadInitialUser();
}
// ❌ Avoid when a signal effect can express the same reactive behavior
ngOnChanges(changes: SimpleChanges) {
if (changes['userId']) {
this.loadUser(changes['userId'].currentValue);
}
}
// ✅ Preferred: Signals + effect for reactive updates
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);
private readonly userEffect = effect(() => {
// Runs automatically when userId() changes
this.loadUser(this.userId());
});
// ✅ For derived data, use computed
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()` |
```typescript
// Cleanup example
private readonly destroyRef = inject(DestroyRef);
constructor() {
const subscription = someObservable$.subscribe();
this.destroyRef.onDestroy(() => subscription.unsubscribe());
}
```
---
## inject() Over Constructor (REQUIRED)
```typescript
// ✅ ALWAYS
private readonly http = inject(HttpClient);
// ❌ NEVER
constructor(private http: HttpClient) {}
```
---
## Native Control Flow (REQUIRED)
```html
@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 |
```typescript
// ✅ 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: [] });
```
---
## Zoneless Angular (REQUIRED)
Angular is zoneless. Use `provideZonelessChangeDetection()`.
```typescript
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()]
});
```
Remove ZoneJS:
```bash
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
- https://angular.dev/guide/signals
- https://angular.dev/guide/templates/control-flow
- https://angular.dev/guide/zoneless
## Imported Angular Signals Reference Pack
Additional Angular signal patterns were imported under `references/frontend-pack-angular-signals/`:
- `overview.md` — `signal`, `computed`, `linkedSignal`, `effect`, RxJS interop
- `references/signal-patterns.md` — component/service state patterns and edge cases
Use these as supporting references. Prefer the canonical `angular-core` guidance when conflicts appear.
---
name: angular-core-23f1f7c3c03e
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.
```typescript
// ✅ ALWAYS - External template
@Component({
selector: 'app-user',
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './user.component.html',
})
export class UserComponent {}
```
```html
<!-- user.component.html -->
<h2>{{ displayName() }}</h2>
```
```typescript
// ❌ NEVER - Inlined template
@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)
```typescript
// ✅ 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 for State (REQUIRED)
```typescript
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()));
```
---
## 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.
```typescript
// ✅ Valid: imperative initialization
ngOnInit() {
this.loadInitialUser();
}
// ❌ Avoid when a signal effect can express the same reactive behavior
ngOnChanges(changes: SimpleChanges) {
if (changes['userId']) {
this.loadUser(changes['userId'].currentValue);
}
}
// ✅ Preferred: Signals + effect for reactive updates
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);
private readonly userEffect = effect(() => {
// Runs automatically when userId() changes
this.loadUser(this.userId());
});
// ✅ For derived data, use computed
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()` |
```typescript
// Cleanup example
private readonly destroyRef = inject(DestroyRef);
constructor() {
const subscription = someObservable$.subscribe();
this.destroyRef.onDestroy(() => subscription.unsubscribe());
}
```
---
## inject() Over Constructor (REQUIRED)
```typescript
// ✅ ALWAYS
private readonly http = inject(HttpClient);
// ❌ NEVER
constructor(private http: HttpClient) {}
```
---
## Native Control Flow (REQUIRED)
```html
@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 |
```typescript
// ✅ 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: [] });
```
---
## Zoneless Angular (REQUIRED)
Angular is zoneless. Use `provideZonelessChangeDetection()`.
```typescript
bootstrapApplication(AppComponent, {
providers: [provideZonelessChangeDetection()]
});
```
Remove ZoneJS:
```bash
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
- https://angular.dev/guide/signals
- https://angular.dev/guide/templates/control-flow
- https://angular.dev/guide/zoneless
## Imported Angular Signals Reference Pack
Additional Angular signal patterns were imported under `references/frontend-pack-angular-signals/`:
- `overview.md` — `signal`, `computed`, `linkedSignal`, `effect`, RxJS interop
- `references/signal-patterns.md` — component/service state patterns and edge cases
Use these as supporting references. Prefer the canonical `angular-core` guidance when conflicts appear.
Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.