Angular • TypeScript • Performance
Modern Angular 19+ patterns
import { inject } from '@angular/core';
// ✅ Modern: inject() function (no constructor needed!)
private service = inject(MyService);
private router = inject(Router);
// ngOnInit - initialization logic
ngOnInit(): void {
this.loadData();
}
// ngOnChanges - react to input changes (rarely needed with signals!)
ngOnChanges(changes: SimpleChanges): void {
if (changes['data']?.currentValue) {
this.processData();
}
}
// ngOnDestroy - cleanup subscriptions
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
Modern reactive state management
import { signal, computed, effect } from '@angular/core';
// Writable signal
count = signal(0);
// Computed signal
doubled = computed(() => this.count() * 2);
// Effect - runs on signal changes
constructor() {
effect(() => {
console.log('Count changed:', this.count());
});
}
// Update signal
increment() {
this.count.update(v => v + 1);
// or: this.count.set(5);
}
Angular 17+ built-in control flow (no CommonModule!)
// ✅ Modern @if (no *ngIf)
@if (user()) {
<div>Welcome {{ user().name }}</div>
} @else {
<div>Please log in</div>
}
// ✅ Modern @for (no *ngFor, built-in trackBy!)
@for (item of items(); track item.id) {
<div>{{ item.name }}</div>
} @empty {
<div>No items found</div>
}
// ✅ Modern @switch
@switch (status()) {
@case ('loading') { <spinner /> }
@case ('error') { <error-msg /> }
@case ('success') { <data-view /> }
@default { <div>Unknown</div> }
}
Angular 17.1+ signal inputs/outputs
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div>{{ user().name }}</div>
<button (click)="handleClick()">Select</button>
`
})
export class UserCardComponent {
// ✅ Modern: signal-based input
user = input.required<User>();
// Optional with default
theme = input('dark');
// ✅ Modern: output (replaces EventEmitter)
userSelected = output<User>();
handleClick() {
this.userSelected.emit(this.user());
}
}
No NgModules needed (default in Angular 19)
import { Component, inject } from '@angular/core';
@Component({
selector: 'app-my-component',
standalone: true, // Default in new projects!
template: `
@if (data()) {
<h1>{{ data().title }}</h1>
}
`
})
export class MyComponent {
private service = inject(DataService);
data = this.service.getData();
}
Optimize performance with OnPush strategy
import { ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-optimized',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class OptimizedComponent {
// Only checks when:
// - Input changes (reference)
// - Event fires from template
// - Observable emits (async pipe)
// - Manual: cdr.markForCheck()
}
Built-in TypeScript helpers
interface User {
id: number;
name: string;
email: string;
}
// Partial - all props optional
type PartialUser = Partial;
// Required - all props required
type RequiredUser = Required;
// Pick - select specific props
type UserPreview = Pick;
// Omit - exclude props
type UserWithoutEmail = Omit;
// Record - key-value map
type UserMap = Record;
Runtime type checking
// Simple type guard
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// Object type guard
interface Cat { meow(): void; }
interface Dog { bark(): void; }
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal;
}
// Usage
if (isCat(myPet)) {
myPet.meow(); // TypeScript knows it's a Cat
}
Reusable type-safe code
// Generic function
function firstElement(arr: T[]): T | undefined {
return arr[0];
}
// Generic class
class DataStore {
private data: T[] = [];
add(item: T): void {
this.data.push(item);
}
get(index: number): T | undefined {
return this.data[index];
}
}
// Generic constraints
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
Most-used RxJS operators
import { map, filter, switchMap, debounceTime,
distinctUntilChanged, takeUntil } from 'rxjs/operators';
// Transform values
data$.pipe(map(x => x * 2));
// Filter values
data$.pipe(filter(x => x > 10));
// Switch to new observable (cancel previous)
search$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.api.search(term))
);
// Auto-unsubscribe pattern
private destroy$ = new Subject();
ngOnInit() {
data$.pipe(
takeUntil(this.destroy$)
).subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
Different Subject behaviors
import { Subject, BehaviorSubject, ReplaySubject, AsyncSubject } from 'rxjs';
// Subject - no initial value, no replay
const subject = new Subject();
// BehaviorSubject - has initial value, replays last
const behavior = new BehaviorSubject(0);
// ReplaySubject - replays N previous values
const replay = new ReplaySubject(3);
// AsyncSubject - emits only last value on complete
const async = new AsyncSubject();
Load features on-demand
// app-routing.module.ts
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module')
.then(m => m.AdminModule)
},
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component')
.then(m => m.DashboardComponent) // Standalone
}
];
Optimize *ngFor rendering
// Template
{{ item.name }}
// Component
trackByFn(index: number, item: any): any {
return item.id; // unique identifier
}
// Without trackBy: Angular re-renders entire list
// With trackBy: Angular only re-renders changed items
Reduce production bundle
// angular.json - production config
"optimization": true,
"buildOptimizer": true,
"aot": true,
"sourceMap": false,
// Tree-shakeable providers
@Injectable({ providedIn: 'root' })
// Analyze bundle
ng build --stats-json
npx webpack-bundle-analyzer dist/*/stats.json
// Dynamic imports
const module = await import('./heavy-module');
// Remove unused code
// Use barrel imports carefully
// Lazy load routes
// Use OnPush change detection