---
name: angular-development
description: Write production-quality modern Angular (v17+) code day to day — standalone components with signal inputs/outputs, computed and effect done right, templates with @if/@for/@switch/@defer, typed reactive forms, HttpClient services with interceptors, RxJS-to-signal conversion, dependency injection with inject(), routing with lazy routes and input binding, directives and pipes, Angular Material/CDK usage, styling and accessibility. Use this whenever the user asks to build, implement, refactor or fix an Angular component, service, directive, pipe, form, dialog, table, or feature; asks "how do I do X in Angular"; pastes Angular code with a bug; or asks about signals vs RxJS, @Input vs input(), ngOnInit vs constructor, ViewChild, content projection, or template syntax. Also use it when generating any new Angular code, even small snippets.
metadata:
  technology: Angular
  type: development
---

# Angular Development

Modern Angular code is small standalone components, signals for state, RxJS only at the edges, and templates that contain no logic beyond bindings. This skill is the implementation guide; architecture, performance and testing have their own skills. If the codebase is still on NgModules/`@Input()`, write new code in the modern style anyway — it coexists.

## 1. Before writing a component

1. **Smart or presentational?** Pages/containers inject stores and services; everything else takes `input()` and emits `output()`.
2. **What state does it own?** `signal()` for owned state; `computed()` for everything derivable; nothing derived stored in a field.
3. **Which UI states?** loading / empty / error / success — each has a template branch.
4. **Accessible role?** Use Material/CDK or semantic HTML; never a `div (click)`.

## 2. Component template

```ts
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
import { CurrencyPipe } from '@angular/common';

@Component({
  selector: 'app-order-row',
  imports: [CurrencyPipe],
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: { class: 'order-row', '[attr.aria-selected]': 'selected()' },
  template: `
    <button type="button" (click)="select.emit(order().id)" [attr.aria-label]="'Open order ' + order().number">
      <span>{{ order().number }}</span>
      <span>{{ order().total | currency: order().currency }}</span>
      @if (isOverdue()) { <app-badge tone="warning">Overdue</app-badge> }
    </button>
  `,
})
export class OrderRowComponent {
  readonly order = input.required<Order>();
  readonly selected = input(false, { transform: booleanAttribute });
  readonly select = output<Order['id']>();

  protected readonly isOverdue = computed(() => this.order().dueDate < Date.now() && !this.order().paid);
}
```

Conventions:
- **Standalone** (default), **`OnPush`** always, `host: {}` for host bindings/classes/listeners.
- **`input()` / `input.required()` / `output()` / `model()`** — never `@Input()`/`@Output()` in new code. `transform: booleanAttribute | numberAttribute` for attribute-style inputs.
- **`computed()` for derived values**; `protected readonly` so templates can read them and tests can't poke them.
- **`inject()` in field initialisers**; the constructor stays empty or trivial. No `ngOnInit` for things that can be field initialisers or `computed`.
- **Inline template for < ~30 lines**, otherwise `templateUrl`. One component per file; selector prefix from the workspace (`app-`, or the lib prefix).
- **Component under ~200 lines**; extract child components or a service beyond that.
- **`viewChild()` / `contentChild()` signal queries** instead of decorators; `viewChild.required` when it must exist.

## 3. Signals and effects

| Want to… | Use |
|---|---|
| Hold state | `signal(initial)`; update with `.set()` / `.update(fn)` — never mutate the object inside |
| Derive | `computed()` — lazy, memoised, synchronous |
| Derive but allow local override (e.g. selected item resets when list changes) | `linkedSignal()` |
| Async data as state | `toSignal(obs$, { initialValue })`, `resource()` / `rxResource()` / `httpResource()` where available |
| Side effect on change (log, localStorage, imperative DOM/3rd-party API) | `effect()` — the *only* legitimate use |
| Signal → Observable (feed an RxJS pipeline) | `toObservable(sig)` |

`effect()` rules: no signal writes inside (if you need one, you wanted `computed`/`linkedSignal`); `untracked()` for reads that shouldn't be dependencies; cleanup via the `onCleanup` callback; keep them rare and named by intent in a comment.

Arrays/objects in signals are immutable: `items.update(list => [...list, x])`, `form.update(f => ({ ...f, name }))`.

## 4. Templates

- **Control flow**: `@if (x(); as value) {…} @else if {…} @else {…}`, `@for (item of items(); track item.id) {…} @empty {…}`, `@switch`. `track` is mandatory and must be a stable id.
- **`@defer`** for heavy, below-the-fold or interaction-gated components; always give `@placeholder` and `@loading`.
- **No function calls in bindings** except signals/computeds and pure pipes. `{{ total() }}` fine; `{{ calcTotal(items) }}` not.
- **Bindings**: `[prop]`, `(event)`, `[(model)]` two-way with `model()` inputs; `[attr.aria-*]` for ARIA; `[class.active]`, `[style.width.px]`.
- **Pipes** for formatting (`date`, `currency`, `number`, custom pure pipes). Custom pipe = pure function with `standalone: true`.
- **Content projection**: `<ng-content select="[slot=actions]">`; `ng-template` + `ngTemplateOutlet` for consumer-supplied templates.
- Template reference variables `#input` + `viewChild('input')` for focus management.
- Keep templates declarative: no business rules; if a condition needs three operators, make it a `computed`.

## 5. Services and HTTP

```ts
@Injectable({ providedIn: 'root' })
export class OrdersApi {
  private readonly http = inject(HttpClient);
  private readonly base = inject(API_BASE_URL);

  list(filters: OrderFilters): Observable<Page<Order>> {
    return this.http.get<Page<Order>>(`${this.base}/orders`, { params: toParams(filters) });
  }
  update(id: string, patch: Partial<Order>): Observable<Order> {
    return this.http.patch<Order>(`${this.base}/orders/${id}`, patch);
  }
}
```

- **API class per resource** returning typed Observables; no component touches `HttpClient`.
- Response types from OpenAPI codegen; DTO → domain mapping in the API class if shapes differ.
- **Functional interceptors** for auth, base URL, error normalisation, retry-with-backoff on idempotent GETs.
- Consume in a **feature store** (signals or SignalStore) or `toSignal`/`httpResource` in the page; components get signals.
- Cancel in-flight requests with `switchMap` on param changes; `takeUntilDestroyed()` on any manual subscription (there should be few).
- Errors: catch in the store, map to a user message, expose `error = signal<string | null>(null)`.

## 6. Forms

- **Typed Reactive Forms**: `new FormGroup({ email: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] }) })` or `inject(NonNullableFormBuilder)`.
- Validation messages in the template via a small `app-field-error` component reading `control.errors` + `touched`; `aria-invalid`/`aria-describedby` wired.
- `form.getRawValue()` typed on submit; disable submit while `pending()` signal is true; `form.markAllAsTouched()` on invalid submit.
- Cross-field validators as `ValidatorFn` on the group; async validators only for server checks, debounced with `updateOn: 'blur'`.
- Dynamic lists → `FormArray`; large forms split into child components that receive the sub-`FormGroup` as an `input.required()`.
- Watch Signal Forms as it stabilises; keep validation rules in one place regardless.

## 7. Routing (implementation)

- `provideRouter(routes, withComponentInputBinding(), withViewTransitions())`; route params/query params arrive as `input()` signals on the routed component.
- `loadComponent`/`loadChildren` for every feature route; route `title` set for a11y and analytics.
- Functional `CanActivateFn` guards using `inject()`; return `UrlTree` for redirects.
- Navigate with `inject(Router).navigate([...])` or `<a [routerLink]>`; `routerLinkActive` for nav state.
- Resolvers only for must-have-before-render data; otherwise skeleton + load in component.

## 8. Directives and pipes

- Attribute directive for cross-cutting behaviour (`appAutofocus`, `appHasPermission`, `appTooltip`) with `inject(ElementRef)`, `input()`s, `host` bindings, and `DestroyRef` for cleanup.
- Structural behaviour via `ngTemplateOutlet` or `createEmbeddedView` — but prefer `@if` in templates; custom structural directives are rare now.
- `hostDirectives` to compose behaviours onto components (e.g. `CdkTrapFocus`).
- Pipes: pure, stateless, small; inject services only when necessary.

## 9. Material / CDK usage

- Import each Material component into the standalone `imports` array (`MatButtonModule`, `MatTableModule`, …).
- Theme via M3 tokens in `styles.scss` (`mat.theme(...)`); component-level colour via CSS custom properties, not `::ng-deep`.
- Dialogs: `inject(MatDialog).open(EditOrderDialog, { data })` returning typed results; `MatDialogRef` in the dialog component; focus restored automatically.
- CDK: `Overlay` for custom popovers, `A11yModule` (`cdkTrapFocus`, `LiveAnnouncer`), `DragDrop`, `ScrollingModule` for virtual lists, `Clipboard`, `Layout` (`BreakpointObserver` → `toSignal`).

## 10. Styling and accessibility

- Component styles scoped by default; `:host` for root; CSS custom properties for theming; no `::ng-deep` (use styling hooks or global tokens).
- Layout with CSS grid/flex; spacing tokens; `@media (prefers-reduced-motion)` respected in animations (`@angular/animations` or CSS).
- Semantic HTML + Material components give most a11y; add `aria-*` via `[attr.]`, manage focus on dialogs/route change (`LiveAnnouncer` for async results), ensure `@angular-eslint/template` a11y rules pass.
- i18n: `i18n` attributes / `$localize` or Transloco; no hardcoded user-facing strings in templates.

## 11. Definition of done for a component

- [ ] Standalone, OnPush, signal inputs/outputs, `track` on every `@for`
- [ ] No derived state in fields; no `effect()` writing signals; no `subscribe()` without teardown
- [ ] Loading/empty/error/success branches rendered
- [ ] Keyboard-operable, labelled, focus managed
- [ ] Types strict, no `any`; `strictTemplates` passes
- [ ] Spec covering main states (see `angular-testing`); story if in `shared/ui`

## Anti-patterns to fix on sight

- `@Input()`/`@Output()`, `@ViewChild`, constructor injection in new code.
- `effect(() => this.x.set(...))` — use `computed`/`linkedSignal`.
- `BehaviorSubject` + `.value` as state; `subscribe()` in `ngOnInit` with no `takeUntilDestroyed`.
- `*ngIf`/`*ngFor`, `@for` without `track`, `track $index` on reorderable data.
- Function calls or `new Date()` in templates; impure pipes for formatting.
- `HttpClient` inside components; business logic in components; `any` in templates.
- `::ng-deep`, `ViewEncapsulation.None` to style Material internals.
- Mutating a signal's object in place (`this.items().push(x)`).
- `setTimeout` to fix `ExpressionChangedAfterItHasBeenChecked`.
