Skip to main content

Angular 15-18 Quick Reference

Modern Features Matrix

FeatureVersionStatusUse When
Standalone Components15StableAll new projects
Signals16-17StableLocal reactive state
New Control Flow17StableAll templates
Signal Inputs17.1StableComponent inputs
Zoneless CD18+ExperimentalMax performance
SSR Hydration16+StableSEO, performance

Change Detection Strategies

StrategyPerformanceUse CaseProsCons
Default★☆☆PrototypesEasySlow
OnPush★★★ProductionFastManual triggers
Signals★★★★★Modern appsFastestLearning curve
Zoneless★★★★★Performance-criticalPredictableRequires signals

State Management Decision Tree

How large is your state?

├─ Component-level
│ └─ Use: Signals
│ const count = signal(0);

├─ Feature-level
│ └─ Use: Signal Store
│ @Injectable() class FeatureStore { }

└─ Application-level
└─ Use: NgRx + Signals
Store with signal selectors

Performance Optimization Checklist

✅ Must Have

  • OnPush change detection
  • Lazy loading for routes
  • TrackBy in @for loops
  • NgOptimizedImage for images
  • Bundle size < 500KB

✅ Should Have

  • Preloading strategy
  • @defer for heavy components
  • Code splitting
  • Tree-shaking optimization
  • CDN for static assets

✅ Nice to Have

  • SSR with hydration
  • Service Worker (PWA)
  • Image CDN integration
  • Zoneless mode
  • Compression (Brotli)

Common Patterns

1. Signal Component Store

@Injectable()
export class TodoStore {
private todos = signal<Todo[]>([]);
private filter = signal<"all" | "active" | "completed">("all");

readonly todos$ = this.todos.asReadonly();
readonly filteredTodos = computed(() => {
const todos = this.todos();
const filter = this.filter();
if (filter === "all") return todos;
return todos.filter((t) => (filter === "active" ? !t.completed : t.completed));
});

addTodo(text: string) {
this.todos.update((todos) => [...todos, { id: Date.now(), text, completed: false }]);
}

setFilter(filter: "all" | "active" | "completed") {
this.filter.set(filter);
}
}

2. Functional Guard

export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
const router = inject(Router);

if (authService.isAuthenticated()) {
return true;
}

return router.createUrlTree(["/login"], {
queryParams: { returnUrl: state.url },
});
};

3. Functional Resolver

export const userResolver: ResolveFn<User> = (route, state) => {
const userService = inject(UserService);
const id = route.paramMap.get("id")!;
return userService.getUser(id);
};

4. Custom Pipe

@Pipe({ name: "timeAgo", standalone: true })
export class TimeAgoPipe implements PipeTransform {
transform(value: Date): string {
const seconds = Math.floor((Date.now() - value.getTime()) / 1000);

if (seconds < 60) return "just now";
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
return `${Math.floor(seconds / 86400)}d ago`;
}
}

5. HTTP Interceptor

export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.getToken();

if (token) {
req = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
}

return next(req);
};

// Provide in main.ts
provideHttpClient(withInterceptors([authInterceptor]));

API Quick Reference

Signals

// Create
const count = signal(0);
const user = signal<User | null>(null);

// Read
console.log(count());

// Write
count.set(10);
count.update((n) => n + 1);

// Computed
const doubled = computed(() => count() * 2);

// Effect
effect(() => console.log("Count:", count()));

// Readonly
const readonlyCount = count.asReadonly();

Control Flow

// @if
@if (condition) { <div>True</div> } @else { <div>False</div> }

// @for
@for (item of items; track item.id) { <div>{{ item.name }}</div> }

// @switch
@switch (value) {
@case (1) { <div>One</div> }
@default { <div>Other</div> }
}

// @defer
@defer (on viewport) {
<heavy-component />
} @loading { <spinner /> } @placeholder { <skeleton /> }

Routing

// Route config
export const routes: Routes = [
{
path: "feature",
loadComponent: () => import("./feature.component").then((m) => m.FeatureComponent),
canActivate: [authGuard],
resolve: { data: dataResolver },
},
];

// Bootstrap
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes, withPreloading(PreloadAllModules)), provideHttpClient()],
});

CLI Commands

# Create standalone component
ng generate component my-component --standalone

# Create standalone pipe
ng generate pipe my-pipe --standalone

# Create service
ng generate service my-service

# Migrate to control flow
ng generate @angular/core:control-flow

# Build for production
ng build --configuration production

# Analyze bundle
ng build --stats-json
npx webpack-bundle-analyzer dist/stats.json

Interview Quick Prep

30-Second Explanations

Standalone Components: "Self-contained Angular components that don't need NgModules. They declare their dependencies directly, enabling better tree-shaking and simpler architecture."

Signals: "Fine-grained reactive primitives that automatically track dependencies and update only affected DOM nodes, eliminating Zone.js overhead."

OnPush Strategy: "Change detection strategy that only checks components when inputs change by reference or events fire, dramatically improving performance."

@defer: "Declarative lazy loading in templates with triggers like viewport, interaction, or idle, plus built-in loading/error states."

Zoneless: "Running Angular without Zone.js by using signals for reactivity, providing predictable performance and smaller bundles."

Resources

Official Documentation

Learning

Tools

Community


Keep this reference handy during development and interviews!