Angular Cheat Sheet
A quick-reference guide to the most frequently used Angular syntax, decorators, and patterns. Bookmark this page for fast lookups while you code.
Component Anatomy
TS
import { Component, Input, Output, EventEmitter, OnInit, inject,
ChangeDetectionStrategy, signal } from '@angular/core';
@Component({
standalone: true, // No NgModule needed
selector: 'app-example', // HTML tag: <app-example />
templateUrl: './example.component.html', // Or: template: `...`
styleUrl: './example.component.scss', // Or: styles: [`...`]
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [OtherComponent, SomePipe], // Import what this component uses
})
export class ExampleComponent implements OnInit {
// Inputs
@Input({ required: true }) title!: string;
@Input({ alias: 'size' }) buttonSize: 'sm' | 'lg' = 'sm';
// Outputs
@Output() clicked = new EventEmitter<string>();
// Signals
count = signal(0);
// Dependency injection
private router = inject(Router);
ngOnInit() { /* runs once after component creation */ }
onClick() {
this.clicked.emit('clicked!');
this.count.update((n) => n + 1);
}
}Lifecycle Hooks
Hook | When It Runs |
|---|---|
ngOnChanges(changes) | When input properties change (before ngOnInit on first run) |
ngOnInit() | Once after the first ngOnChanges |
ngDoCheck() | On every change detection run (expensive — use sparingly) |
ngAfterContentInit() | After ng-content is projected into the component |
ngAfterContentChecked() | After projected content is checked |
ngAfterViewInit() | After the component view and child views are initialized |
ngAfterViewChecked() | After the component view is checked |
ngOnDestroy() | Just before Angular destroys the component |
Template Syntax
HTML
<!-- Interpolation -->
<p>{{ expression }}</p>
<p>{{ user?.name ?? 'Guest' }}</p>
<!-- Property binding -->
<input [value]="name" />
<div [class.active]="isActive" />
<button [disabled]="isLoading" />
<!-- Event binding -->
<button (click)="onClick()">Click</button>
<input (keyup.enter)="onEnter()" />
<form (ngSubmit)="onSubmit()" />
<!-- Two-way binding -->
<input [(ngModel)]="username" />
<!-- Attribute binding -->
<td [attr.colspan]="colSpan">...</td>
<!-- Template reference variable -->
<input #myInput />
<button (click)="console.log(myInput.value)">Log</button>
<!-- Pipe -->
<p>{{ price | currency:'USD' }}</p>
<p>{{ name | uppercase | trim }}</p>Control Flow (@if, @for, @switch)
HTML
<!-- @if / @else if / @else -->
@if (user) {
<p>Welcome, {{ user.name }}</p>
} @else if (loading) {
<p>Loading...</p>
} @else {
<p>Please log in</p>
}
<!-- @for -->
@for (item of items; track item.id) {
<li>{{ item.name }}</li>
} @empty {
<li>No items found</li>
}
<!-- @switch -->
@switch (status) {
@case ('active') { <span class="green">Active</span> }
@case ('inactive') { <span class="red">Inactive</span> }
@default { <span>Unknown</span> }
}
<!-- @defer -->
@defer (on viewport) {
<app-heavy-component />
} @placeholder {
<div>Coming soon...</div>
} @loading { <app-spinner /> }Signals
TS
import { signal, computed, effect, toSignal, toObservable } from '@angular/core';
// Create a signal
const count = signal(0);
// Read — always call it as a function
console.log(count()); // 0
// Update
count.set(5); // Set to specific value
count.update((n) => n + 1); // Update based on current value
count.mutate((arr) => arr.push(1)); // Mutate in place (arrays/objects)
// Computed — derived signal, lazy and cached
const doubled = computed(() => count() * 2);
// Effect — side effect when signals change
const cleanup = effect(() => {
console.log('count is now:', count()); // Runs whenever count changes
});
cleanup.destroy(); // Stop the effect
// Signal ↔ Observable interop
const count$ = toObservable(count); // Signal → Observable
const inputSig = toSignal(input$, { initialValue: '' }); // Observable → SignalDirectives
TS
// Attribute directive
@Directive({
standalone: true,
selector: '[appHighlight]',
host: {
'(mouseenter)': 'onEnter()',
'[style.color]': 'color',
},
})
export class HighlightDirective {
@Input() appHighlight = 'yellow';
color = 'black';
onEnter() { this.color = this.appHighlight; }
}
// Structural directive — use built-in @if/@for whenever possible
// For custom: inject TemplateRef and ViewContainerRefServices and Dependency Injection
TS
// Provided in root — singleton, tree-shakeable
@Injectable({ providedIn: 'root' })
export class GlobalService {}
// Provided at component level — new instance per component
@Component({ providers: [LocalService] })
export class MyComponent {}
// Injection tokens
const THEME = new InjectionToken<string>('theme');
providers: [{ provide: THEME, useValue: 'dark' }]
const theme = inject(THEME);
// Factory provider
providers: [{
provide: LogService,
useFactory: (http: HttpClient) => new LogService(http, 'production'),
deps: [HttpClient],
}]Routing
TS
// Routes definition
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'users/:id', component: UserDetailComponent },
{ path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes) },
{ path: '**', redirectTo: '' },
];
// In a component
const router = inject(Router);
const route = inject(ActivatedRoute);
// Read params
const id = route.snapshot.paramMap.get('id');
const id$ = route.paramMap.pipe(map(p => p.get('id')));
// Navigate
router.navigate(['/users', userId]);
router.navigate(['../'], { relativeTo: route });
router.navigateByUrl('/dashboard?tab=overview');Forms
TS
import { FormGroup, FormControl, FormArray, Validators } from '@angular/forms';
// Reactive form
form = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(8)]),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl(''),
}),
phones: new FormArray([new FormControl('')]),
});
// Access values and state
form.value // { email: '', password: '' }
form.valid // true/false
form.get('email')?.errors // null or { required: true }
form.get('email')?.touched // true/false
// FormArray
const phones = form.get('phones') as FormArray;
phones.push(new FormControl(''));
phones.removeAt(0);HTML
<!-- Template-driven form -->
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
<input name="email" ngModel required email />
<button [disabled]="f.invalid">Submit</button>
</form>
<!-- Reactive form -->
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="email" />
<input formControlName="password" />
<div formGroupName="address">
<input formControlName="city" />
</div>
<button [disabled]="form.invalid">Submit</button>
</form>HTTP Client
TS
private http = inject(HttpClient);
// GET
http.get<User[]>('/api/users')
http.get<User>(`/api/users/${id}`)
http.get<User[]>('/api/users', { params: { page: '1', limit: '10' } })
// POST
http.post<User>('/api/users', { name: 'Alice', email: 'alice@example.com' })
// PUT / PATCH
http.put<User>(`/api/users/${id}`, updatedUser)
http.patch<User>(`/api/users/${id}`, { name: 'Alice' })
// DELETE
http.delete(`/api/users/${id}`)
// With headers
http.get('/api/data', {
headers: new HttpHeaders({ Authorization: `Bearer ${token}` }),
})
// Interceptor
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).getToken();
return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
};RxJS Quick Reference
TS
import { of, from, interval, timer, combineLatest, forkJoin, merge } from 'rxjs';
import { map, filter, switchMap, mergeMap, concatMap, exhaustMap,
catchError, tap, take, debounceTime, distinctUntilChanged,
shareReplay, startWith, withLatestFrom } from 'rxjs/operators';
// Creation
of(1, 2, 3) // Emit values synchronously
from([1, 2, 3]) // From array/promise
interval(1000) // Emit 0,1,2... every second
timer(1000, 500) // After 1s, then every 500ms
// Transformation
map(x => x * 2)
switchMap(id => http.get(`/api/${id}`)) // Cancel previous, use latest
mergeMap(id => http.get(`/api/${id}`)) // Parallel, all results
concatMap(id => http.get(`/api/${id}`)) // Sequential, in order
exhaustMap(id => http.get(`/api/${id}`)) // Ignore new until current completes
// Filtering
filter(x => x > 0)
take(5)
debounceTime(300)
distinctUntilChanged()
// Combination
combineLatest([a$, b$]) // Latest from all, on any change
forkJoin([a$, b$]) // Wait for all to complete
merge(a$, b$) // Merge multiple streams
// Error handling
catchError(err => of(defaultValue))
retry(3)
// Utility
tap(val => console.log(val))
shareReplay(1) // Cache and replay last valuePipes Quick Reference
HTML
<!-- Built-in pipes -->
{{ name | uppercase }} <!-- ALICE -->
{{ name | lowercase }} <!-- alice -->
{{ name | titlecase }} <!-- Alice Smith -->
{{ 'hello world' | slice:0:5 }} <!-- hello -->
{{ 3.14159 | number:'1.2-2' }} <!-- 3.14 -->
{{ 1234.56 | currency:'USD' }} <!-- $1,234.56 -->
{{ 0.25 | percent }} <!-- 25% -->
{{ today | date:'shortDate' }} <!-- 7/1/26 -->
{{ today | date:'fullDate' }} <!-- Tuesday, July 1, 2026 -->
{{ today | date:'HH:mm:ss' }} <!-- 14:30:00 -->
{{ items | json }} <!-- Debug: pretty-print object -->
{{ observable$ | async }} <!-- Subscribe + unsubscribe automatically -->
{{ items | keyvalue }} <!-- Iterate object key-value pairs -->
{{ items | slice:0:5 }} <!-- First 5 items of array -->CLI Commands
Bash
# Generate ng g component features/user-list # Component ng g service core/auth # Service ng g directive shared/highlight # Directive ng g pipe shared/truncate # Pipe ng g guard core/auth --implements CanActivate # Guard ng g interceptor core/http/auth # Interceptor ng g class models/user --type=model # Class ng g interface models/user # Interface ng g enum models/status # Enum # Flags ng g component my-comp --standalone --skip-tests ng g service my-svc --dry-run # Preview without creating # Build ng build # Dev build ng build --configuration=production # Prod build ng build --stats-json # For bundle analysis # Test ng test # Run tests (watch mode) ng test --watch=false --code-coverage # Misc ng update # Update Angular ng add @angular/material # Add Angular Material ng lint # Run ESLint