Angular Standalone Components
Quick Guide: Components are standalone by default in Angular 19. Use
signal(),computed(),effect(),linkedSignal()for reactive state. Useinput(),output(),model()for component communication. Use@if,@for,@switch,@deferfor template control flow. Useinject()for dependency injection. Useresource()for async data fetching.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST write standalone components (the default in Angular 19) - only specify standalone: false when intentionally using NgModules)
(You MUST use input(), output(), model() functions instead of @Input(), @Output() decorators)
(You MUST use inject() function for dependency injection, NOT constructor injection)
(You MUST use @if, @for, @switch control flow blocks, NOT *ngIf, *ngFor, *ngSwitch)
(You MUST use track expression in ALL @for loops)
(You MUST use linkedSignal() instead of manual signal synchronization for dependent writable state)
</critical_requirements>
Auto-detection: Angular component, standalone component, signal, computed, effect, linkedSignal, resource, rxResource, httpResource, input(), output(), model(), @if, @for, @switch, @defer, inject(), provideRouter, afterRenderEffect
When to use:
- Building Angular 17-19 components with standalone architecture
- Implementing reactive state with signals
- Creating component communication with signal-based inputs/outputs
- Setting up routing with standalone components
- Lazy loading components with
@deferorloadComponent - Fetching async data with
resource(),rxResource(), orhttpResource()
Key patterns covered:
- Standalone component architecture (default in Angular 19)
- Signals for reactive state (signal, computed, effect, linkedSignal)
- Resource API for async data (resource, rxResource, httpResource) [experimental]
- Signal-based inputs and outputs (input, output, model)
- Control flow blocks (@if, @for, @switch, @defer)
- Dependency injection with inject()
- Routing with provideRouter and loadComponent
- DOM effects with afterRenderEffect()
When NOT to use:
- Legacy Angular projects that must use NgModules (consult migration guides)
- Simple scripts without Angular framework
Detailed Resources:
- For core code examples, see examples/core.md
- For advanced patterns (@defer, DI config, model(), RxJS interop), see examples/
- For decision frameworks and anti-patterns, see reference.md
<philosophy>
Philosophy
Angular 17-19 embraces a standalone-first architecture that eliminates NgModule boilerplate. In Angular 19, standalone: true is the default - you only need to specify standalone: false for NgModule components. Signals provide synchronous, fine-grained reactivity for predictable state management. The new control flow syntax (@if, @for, @switch, @defer) is built into templates without imports, offering better type narrowing and smaller bundles. Components should be self-contained, lazy-loadable units that declare their own dependencies.
Angular's Four Pillars (17-19):
- Standalone by Default - Components, directives, and pipes are standalone by default in v19
- Signal-Based Reactivity - Synchronous, memoized, fine-grained change detection with
signal(),computed(),linkedSignal() - Built-In Control Flow - Template syntax that requires no imports and optimizes at build time
- Resource API - Experimental async data fetching that integrates with signals (
resource(),rxResource(),httpResource()in 19.2)
<patterns>
Core Patterns
Pattern 1: Standalone Component Structure
All Angular 17-19 components use standalone: true (the default in Angular 19) and declare their own imports.
// user-card.component.ts
import { Component, input, output } from "@angular/core";
import { DatePipe } from "@angular/common";
export type User = {
id: string;
name: string;
email: string;
createdAt: Date;
};
@Component({
selector: "app-user-card",
standalone: true,
imports: [DatePipe],
template: `
<article class="user-card">
<h2>{{ user().name }}</h2>
<p>{{ user().email }}</p>
<time>Joined: {{ user().createdAt | date: "mediumDate" }}</time>
<button (click)="edit.emit(user())">Edit</button>
</article>
`,
})
export class UserCardComponent {
// Signal-based input (required)
user = input.required<User>();
// Signal-based output
edit = output<User>();
}
Why good: standalone: true eliminates NgModule boilerplate, imports array declares dependencies explicitly for tree-shaking, signal-based input() and output() provide type-safe reactive communication, template is colocated for readability
// BAD - Legacy patterns
@Component({
selector: "app-user-card",
template: `...`,
})
export class UserCardComponent {
@Input() user!: User; // Legacy decorator
@Output() edit = new EventEmitter<User>(); // Legacy EventEmitter
}
Why bad: @Input decorator lacks signal reactivity, EventEmitter is less type-safe than output(), non-null assertion (!) hides potential undefined errors, no imports array means dependencies aren't explicit
Pattern 2: Signals for Reactive State
Use signal() for writable state, computed() for derived values, and effect() for side effects. Key rules: always use .set() or .update() for mutations (never mutate the value directly), use computed() for derived values (not methods), and reserve effect() for true side effects (logging, analytics, localStorage).
// Writable signal
count = signal(0);
// Computed signal (read-only, memoized, recalculates only when deps change)
doubleCount = computed(() => this.count() * 2);
// Updating signals - always immutable
this.count.set(5); // Replace value
this.count.update((value) => value + 1); // Update from previous
// For arrays/objects: return new references
items = signal<Item[]>([]);
this.items.update((items) => [...items, newItem]); // Spread, don't push
// Effect for side effects only (not derived state)
effect(() => console.log(`Count: ${this.count()}`));
See examples/core.md for a full shopping cart example with signals.
// BAD - Direct mutation doesn't trigger reactivity
this.items().push(newItem); // signal won't notify consumers
this.items.update(items => { items.push(newItem); return items; }); // same reference, no update
// BAD - Method instead of computed (recalculates every call, not memoized)
getTotal(): number { return this.items().reduce(...); }
Why bad: direct mutation doesn't trigger change detection, returning same reference skips equality check, methods lack memoization that computed() provides
Pattern 3: Signal Inputs and Outputs
Use input(), output(), and model() functions for component communication.
// search-input.component.ts
import { Component, input, output, model, computed } from "@angular/core";
const MIN_SEARCH_LENGTH = 3;
@Component({
selector: "app-search-input",
standalone: true,
template: `
<div class="search-input">
<input
[value]="query()"
(input)="onInput($event)"
[placeholder]="placeholder()"
/>
@if (isValidSearch()) {
<button (click)="search.emit(query())">Search</button>
}
@if (query()) {
<button (click)="clear()">Clear</button>
}
</div>
`,
})
export class SearchInputComponent {
// Optional input with default value
placeholder = input("Search...");
// Required input
minLength = input.required<number>();
// Two-way binding with model()
que