# Standalone Components & Signals ## Standalone Component Pattern ```typescript import { Component, signal, computed, effect } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @Component({ selector: 'app-user-profile', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './user-profile.component.html', styleUrl: './user-profile.component.scss', changeDetection: ChangeDetectionStrategy.OnPush }) export class UserProfileComponent { // Signal-based state count = signal(0); doubleCount = computed(() => this.count() * 2); constructor() { // Side effects effect(() => { console.log(`Count is: ${this.count()}`); }); } increment() { this.count.update(value => value + 1); } } ``` ## Input/Output with Signals ```typescript import { Component, input, output, model } from '@angular/core'; @Component({ selector: 'app-search-box', standalone: true, template: ` ` }) export class SearchBoxComponent { // Signal inputs (Angular 17.1+) placeholder = input('Search...'); initialQuery = input(''); // Signal outputs queryChange = output(); // Two-way binding with model signal query = model(''); onQueryChange(event: Event) { const value = (event.target as HTMLInputElement).value; this.query.set(value); this.queryChange.emit(value); } } // Parent usage @Component({ template: ` ` }) export class ParentComponent { searchQuery = signal(''); onSearch(query: string) { console.log('Searching:', query); } } ``` ## Smart vs Dumb Components ```typescript // Smart Component (Container) @Component({ selector: 'app-users-container', standalone: true, imports: [UserListComponent], template: ` ` }) export class UsersContainerComponent { private usersService = inject(UsersService); users = signal([]); loading = signal(true); constructor() { effect(() => { this.usersService.getUsers().subscribe({ next: users => { this.users.set(users); this.loading.set(false); }, error: err => console.error(err) }); }); } onUserSelected(user: User) { // Handle business logic } } // Dumb Component (Presentational) @Component({ selector: 'app-user-list', standalone: true, imports: [CommonModule], template: ` @if (loading()) {
Loading...
} @else { @for (user of users(); track user.id) {
{{ user.name }}
} } `, changeDetection: ChangeDetectionStrategy.OnPush }) export class UserListComponent { users = input.required(); loading = input(false); userSelected = output(); } ``` ## Content Projection ```typescript // Card component with multiple slots @Component({ selector: 'app-card', standalone: true, template: `
` }) export class CardComponent {} // Usage @Component({ template: `

Card Title

Card content goes here

` }) export class ParentComponent {} ``` ## Dependency Injection ```typescript import { Component, inject } from '@angular/core'; import { UserService } from './user.service'; @Component({ selector: 'app-user-dashboard', standalone: true }) export class UserDashboardComponent { // Modern inject() API private userService = inject(UserService); private router = inject(Router); // Optional dependency private logger = inject(LoggerService, { optional: true }); users = signal([]); ngOnInit() { this.loadUsers(); } loadUsers() { this.userService.getUsers().subscribe({ next: users => this.users.set(users), error: err => this.logger?.error('Failed to load users', err) }); } } ``` ## New Control Flow (@if, @for) ```typescript @Component({ template: ` @if (user(); as currentUser) {
Hello, {{ currentUser.name }}
} @else if (loading()) {
Loading...
} @else {
Please log in
} @for (item of items(); track item.id) {
{{ item.name }}
} @empty {
No items found
} @switch (status()) { @case ('pending') { Pending... } @case ('success') { Success! } @default { Unknown } } ` }) export class ModernControlFlowComponent { user = signal(null); loading = signal(false); items = signal([]); status = signal<'pending' | 'success' | 'error'>('pending'); } ``` ## Performance: OnPush & TrackBy ```typescript @Component({ selector: 'app-product-list', standalone: true, imports: [CommonModule], template: ` @for (product of products(); track trackByProductId($index, product)) { } `, changeDetection: ChangeDetectionStrategy.OnPush }) export class ProductListComponent { products = input.required(); // TrackBy for optimal rendering trackByProductId(index: number, product: Product): number { return product.id; } } ``` ## Quick Reference | Pattern | Angular 17+ Approach | |---------|---------------------| | Component | Standalone by default | | State | Signals (`signal()`, `computed()`) | | Input | `input()`, `input.required()` | | Output | `output()` | | Two-way | `model()` | | DI | `inject()` function | | Control Flow | `@if`, `@for`, `@switch` | | Change Detection | `ChangeDetectionStrategy.OnPush` |