Angular 132 🅰️ Cheatsheet
This cheatsheet is a dense, practical reference for Angular’s core syntax, CLI commands, and modern reactive primitives. It prioritizes the features that appear most frequently in daily development—template binding, component configuration, signals, dependency injection, routing, forms, HTTP, and testing. It reflects Angular 17+ where standalone components are the default, built-in control flow blocks replace the structural directives, and signals are the primary synchronous reactive primitive.
Key point: Angular’s template syntax is HTML augmented with binding brackets and control flow blocks. Signals replace many Observable patterns for synchronous state; RxJS remains the tool for asynchronous streams. Standalone components are the default since Angular 17, and NgModules are optional rather than required.
CLI Commands
| Command | Alias | Purpose |
|---|---|---|
ng new <name> | Create workspace and application | |
ng new <name> --standalone | Explicit standalone (default in 17+) | |
ng new <name> --routing | Include routing module | |
ng serve | Dev server with live reload | |
ng serve --port 4201 | Custom port | |
ng serve --open | -o | Open browser after start |
ng build | b | Compile into dist/ |
ng build --configuration production | Production build (replaces --prod) | |
ng generate <schematic> <name> | g | Scaffold a file |
ng test | Run unit tests (Karma/Jest) | |
ng e2e | Run end-to-end tests | |
ng lint | Run linter | |
ng update | Update Angular packages | |
ng add <package> | Install library with auto-config | |
ng config | Read/write workspace config | |
ng analytics | Enable/disable usage analytics | |
ng completion | Enable shell autocompletion |
Generation schematics: component (c), service (s), module (m), directive (d), pipe (p), guard, interceptor, interface, class, enum, resolver, environments.
ng g c features/user-card # standalone component by default
ng g s core/auth # service with providedIn: 'root'
ng g guard auth --implements=CanActivate
ng g interceptor auth --functional # functional interceptor (v15+)
ng g pipe shared/truncate
Project Structure
| Path | Purpose |
|---|---|
src/app/ | Application code |
src/app/app.component.ts | Root component |
src/app/app.config.ts | App-level providers (v15+) |
src/app/app.routes.ts | Route definitions |
src/main.ts | Bootstrap entry |
src/environments/ | Environment-specific config |
angular.json | Workspace build configuration |
tsconfig.json | TypeScript configuration |
src/styles.css | Global styles |
public/ | Static assets (v17+, was assets/) |
Template Syntax
| Syntax | Purpose | Example |
|---|---|---|
{{ expr }} | Interpolation | {{ user.name }} |
[prop]="expr" | Property binding | [disabled]="isLoading" |
(event)="handler()" | Event binding | (click)="save()" |
[(ngModel)]="prop" | Two-way binding | [(ngModel)]="email" |
[attr.aria-label]="expr" | Attribute binding | [attr.colspan]="colSpan" |
[class.active]="bool" | Class binding | [class.active]="isActive" |
[style.color]="expr" | Style binding | [style.width.px]="width" |
#ref | Template reference | <input #inputRef> |
expr?.prop | Safe navigation | {{ employer?.companyName }} |
expr! | Non-null assertion | {{ user!.name }} |
$event | Event payload | (click)="handle($event)" |
(keyup.enter) | Filtered event | (keyup.enter)="submit()" |
(click.stop) | Event modifier | (click.stop)="handle()" |
Template statements support assignment (=) and chaining (;), but not new, ++, --, +=, or bitwise operators.
Control Flow (Modern Blocks, v17+)
@if (user) {
<p>Welcome, {{ user.name }}</p>
} @else if (user === null) {
<p>Loading...</p>
} @else {
<p>Please log in.</p>
}
@for (item of items; track item.id; let i = $index) {
<li>{{ i }}: {{ item.name }}</li>
} @empty {
<li>No items.</li>
}
@switch (status) {
@case ('loading') { <spinner /> }
@case ('error') { <error-msg /> }
@default { <content /> }
}
@for implicit variables: $index, $count, $first, $last, $even, $odd. The track expression is required for performance and correctness.
Deferred loading (v17+):
@defer (on viewport) {
<heavy-chart />
} @placeholder {
<skeleton />
} @loading {
<spinner />
} @error {
<error-msg />
}
Triggers: on idle, on viewport, on interaction, on hover, on timer(5s), when condition.
Component Configuration
@Component({
selector: 'app-user-card',
standalone: true,
imports: [CommonModule, RouterLink, DatePipe],
templateUrl: './user-card.html',
styleUrls: ['./user-card.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [UserService],
host: { '[class.active]': 'isActive' },
})
export class UserCardComponent {
@Input({ required: true }) name!: string;
@Input() role: string = 'user';
@Output() selected = new EventEmitter<string>();
}
Signal-based inputs/outputs (v17.1+):
import { input, output, model } from '@angular/core';
name = input<string>(); // optional
id = input.required<number>(); // required
role = input('user'); // with default
selected = output<string>(); // replaces EventEmitter
value = model(0); // two-way signal input
Signals
import { signal, computed, effect, linkedSignal } from '@angular/core';
// Writable signal
const count = signal(0);
count(); // read
count.set(5); // write
count.update(c => c + 1); // derive from previous
count.asReadonly(); // expose read-only view
// Computed (lazy, memoized)
const double = computed(() => count() * 2);
// Linked signal (v19): writable, resets from source
const selection = linkedSignal(() => items()[0]);
// Effect (side effects, runs async during change detection)
effect(() => console.log('count:', count()));
// Untracked read (does not create dependency)
import { untracked } from '@angular/core';
effect(() => console.log(count(), untracked(() => other())));
Bridging RxJS and signals:
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
const users = toSignal(users$, { initialValue: [] });
const query$ = toObservable(searchQuery);
Lifecycle Hooks
| Hook | When |
|---|---|
ngOnChanges | Before ngOnInit, when @Input changes |
ngOnInit | Once, after first ngOnChanges |
ngDoCheck | Every change detection run |
ngAfterContentInit | After projected content (ng-content) |
ngAfterContentChecked | After every check of projected content |
ngAfterViewInit | After component view and children |
ngAfterViewChecked | After every check of view |
ngOnDestroy | Before destruction; unsubscribe here |
Functional alternatives:
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
constructor() {
obs$.pipe(takeUntilDestroyed()).subscribe(...);
}
Services and Dependency Injection
@Injectable({ providedIn: 'root' })
export class DataService {
private http = inject(HttpClient);
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
}
// In component:
private data = inject(DataService);
Injection tokens:
export const API_URL = new InjectionToken<string>('API_URL');
// Provide
providers: [{ provide: API_URL, useValue: 'https://api.example.com' }]
// Inject
private url = inject(API_URL);
inject() restrictions: must be called in an injection context (constructor, field initializer, factory function, or runInInjectionContext).
Routing
// app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'users', component: UserListComponent },
{ path: 'users/:id', component: UserDetailComponent },
{
path: 'admin',
loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent),
canActivate: [authGuard],
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.routes').then(m => m.routes),
},
{ path: '**', redirectTo: '' },
];
<router-outlet />
<a routerLink="/users">Users</a>
<a [routerLink]="['/users', id]" routerLinkActive="active">Detail</a>
Reading route parameters:
private route = inject(ActivatedRoute);
private router = inject(Router);
// Snapshot (one-time)
id = this.route.snapshot.paramMap.get('id');
// Observable (changes)
id$ = this.route.paramMap.pipe(map(p => p.get('id')));
// Navigation
this.router.navigate(['/users', 42]);
this.router.navigateByUrl('/users/42');
Functional guards (v15+):
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
return auth.isLoggedIn() || inject(Router).createUrlTree(['/login']);
};
Resolvers:
export const userResolver: ResolveFn<User> = (route) =>
inject(UserService).getUser(route.paramMap.get('id')!);
HTTP Client
import { provideHttpClient, withInterceptors } from '@angular/common/http';
// app.config.ts
providers: [
provideHttpClient(
withInterceptors([authInterceptor, loggingInterceptor]),
),
]
@Injectable({ providedIn: 'root' })
export class ApiService {
private http = inject(HttpClient);
get<T>(url: string): Observable<T> {
return this.http.get<T>(url);
}
post<T>(url: string, body: unknown): Observable<T> {
return this.http.post<T>(url, body);
}
}
Functional interceptor:
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).token();
const cloned = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
return next(cloned);
};
HttpParams and HttpHeaders:
let params = new HttpParams()
.set('page', '1')
.set('limit', '20');
this.http.get('/api/users', { params });
Forms
Reactive forms (preferred):
import { FormGroup, FormControl, Validators } from '@angular/forms';
form = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
age: new FormControl<number>(0, [Validators.min(18)]),
name: new FormGroup({
first: new FormControl(''),
last: new FormControl(''),
}),
});
<form [formGroup]="form" (ngSubmit)="submit()">
<input formControlName="email">
<span *ngIf="form.get('email')?.invalid && form.get('email')?.touched">
Invalid email
</span>
<button [disabled]="form.invalid">Submit</button>
</form>
Template-driven forms:
<form #f="ngForm" (ngSubmit)="submit(f)">
<input name="email" [(ngModel)]="email" required email>
<button [disabled]="f.invalid">Submit</button>
</form>
FormBuilder shorthand:
form = inject(FormBuilder).group({
email: ['', [Validators.required, Validators.email]],
age: [0, Validators.min(18)],
});
Common validators: required, email, min, max, minLength, maxLength, pattern, nullValidator. Custom validator is a function returning ValidationErrors | null.
Pipes
| Pipe | Example | Output |
|---|---|---|
date | {{ createdAt | date:'dd MMM yyyy' }} | 28 Jul 2026 |
currency | {{ price | currency:'EUR' }} | €29.99 |
uppercase | {{ name | uppercase }} | ROHIT |
lowercase | {{ name | lowercase }} | rohit |
titlecase | {{ name | titlecase }} | Rohit |
json | {{ obj | json }} | Pretty JSON |
async | {{ obs$ | async }} | Unwrapped value |
percent | {{ ratio | percent }} | 42% |
number | {{ value | number:'1.2-2' }} | 1,234.56 |
slice | {{ items | slice:0:5 }} | First 5 items |
Custom pipe:
@Pipe({ name: 'truncate', standalone: true })
export class TruncatePipe implements PipeTransform {
transform(value: string, max = 20): string {
return value.length > max ? value.slice(0, max) + '…' : value;
}
}
Date patterns: 'yyyy-MM-dd', 'dd/MM/yy HH:mm', 'EEEE' (full weekday), 'MMM d, y'.
Directives
Attribute directive:
@Directive({ selector: '[appHighlight]', standalone: true })
export class HighlightDirective {
private el = inject(ElementRef);
@Input() appHighlight = 'yellow';
@HostListener('mouseenter')
onEnter() {
this.el.nativeElement.style.background = this.appHighlight;
}
}
Structural directive (custom):
@Directive({ selector: '[appIf]', standalone: true })
export class IfDirective {
private template = inject(TemplateRef);
private vcr = inject(ViewContainerRef);
private hasView = false;
@Input() set appIf(condition: boolean) {
if (condition && !this.hasView) {
this.vcr.createEmbeddedView(this.template);
this.hasView = true;
} else if (!condition && this.hasView) {
this.vcr.clear();
this.hasView = false;
}
}
}
Change Detection
| Strategy | Behavior |
|---|---|
Default | Checks component on every change detection run |
OnPush | Checks only when inputs change, event fires, or signal changes |
ChangeDetectorRef methods:
private cdr = inject(ChangeDetectorRef);
cdr.markForCheck(); // schedule check (OnPush)
cdr.detectChanges(); // run check immediately
cdr.detach(); // stop checking
cdr.reattach(); // resume checking
Zoneless (v18+, experimental):
// app.config.ts
providers: [provideExperimentalZonelessChangeDetection()]
Zoneless change detection relies on signals and explicit markForCheck() calls to schedule updates.
Testing
// Component test
import { TestBed } from '@angular/core/testing';
describe('UserCardComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserCardComponent],
providers: [{ provide: DataService, useValue: mockService }],
}).compileComponents();
});
it('shows the user name', () => {
const fixture = TestBed.createComponent(UserCardComponent);
fixture.componentRef.setInput('name', 'Rohit');
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Rohit');
});
});
// Service test with HttpTestingController
import { provideHttpClientTesting } from '@angular/common/http/testing';
it('fetches users', () => {
const service = TestBed.inject(DataService);
const http = TestBed.inject(HttpTestingController);
service.getUsers().subscribe(users => expect(users.length).toBe(2));
const req = http.expectOne('/api/users');
req.flush([{ id: 1 }, { id: 2 }]);
});
Test utilities: TestBed.createComponent, ComponentFixture, nativeElement, debugElement, setInput() (v15+), fakeAsync/tick, waitForAsync.
Common Pitfalls
| Pitfall | Fix |
|---|---|
Using *ngIf in new projects | Prefer @if blocks (v17+) |
Forgetting track in @for | Required; use unique ID |
| Mutating signal objects directly | Use .update() with immutable pattern |
Missing imports in standalone component | Add dependencies to imports: [] |
| Subscribing without unsubscribing | async pipe or takeUntilDestroyed() |
ng build --prod | Use --configuration production (v13+) |
inject() outside injection context | Call in constructor/field initializer only |
OnPush component not updating | Call markForCheck() or use signals |
Forgetting provideHttpClient() | Add to app.config.ts (v15+) |
ngModel without name in form | Add name attribute or use reactive forms |
Visual: Change Detection with Signals
┌──────────────────────────────────────────────┐
│ SIGNAL UPDATE FLOW │
│ │
│ signal.set(5) │
│ │ │
│ ▼ │
│ Mark consumers dirty │
│ │ │
│ ▼ │
│ Schedule change detection │
│ │ │
│ ▼ │
│ Check OnPush components that read signal │
│ │ │
│ ▼ │
│ Update template bindings │
│ │
│ Unlike zone.js, no DOM event triggers │
│ a global check. Only signal consumers │
│ are marked. │
│ │
└──────────────────────────────────────────────┘
Summary
| Area | Key Takeaway |
|---|---|
| CLI | ng g scaffolds; ng build --configuration production |
| Structure | src/app/, app.config.ts, app.routes.ts |
| Templates | {{ }}, [prop], (event), [(ngModel)] |
| Control flow | @if, @for (with track), @switch, @defer |
| Components | Standalone by default; imports: [] |
| Signals | signal(), computed(), effect(), linkedSignal() |
| Inputs/Outputs | input() / output() / model() |
| Services | @Injectable({ providedIn: 'root' }) + inject() |
| Routing | Lazy load with loadComponent; functional guards |
| HTTP | provideHttpClient(), functional interceptors |
| Forms | Reactive: FormGroup, FormControl, Validators |
| Pipes | date, currency, async, json; custom via @Pipe |
| Testing | TestBed, HttpTestingController, setInput() |
| Change detection | OnPush + signals; zoneless in v18+ |
Angular’s modern surface is signals, standalone components, built-in control flow, and functional interceptors. The cheatsheet is a map; the official Angular documentation at angular.dev is the territory. Angular’s release cadence moves fast—features stabilize across versions, and the cheat sheet reflects the stable core as of Angular 17–19. When a syntax detail matters, verify against the version you are using.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!