ANGULAR MASTER
Home

Angular Architecture & RxJS

Practical senior topics: feature boundaries, DI, standalone, RxJS patterns, leaks, change detection, and state.

1. Feature Architecture (How to organize a real app)

Interviewers want structure that scales: separation by feature, clear public APIs, and minimal coupling.

✅ Recommended

  • Organize by feature (not by type).
  • Expose a public API (index.ts) per feature.
  • Keep shared limited (UI kit, utils, primitives).
  • Put domain-ish logic in services/facades (not in components).

🚨 Traps

  • God shared module that imports everything.
  • “components/services/models” by type (hard to scale).
  • Business rules inside templates/components.
  • Cross-feature imports without a clear boundary.
// Example folder structure
// src/app
// core/ (singletons: auth, interceptors, config)
// shared/ (ui, pipes, utils - no feature dependencies)
// features/
// orders/
// data-access/ (api, repositories)
// ui/ (presentational components)
// feature/ (smart/container components)
// orders.routes.ts

2. Smart vs Dumb Components (Container/Presentational)

This is how you keep templates simple and logic reusable.

Smart (Container)

  • Fetches data / talks to facades.
  • Orchestrates side effects.
  • Passes data down via Inputs.

Dumb (Presentational)

  • Pure UI: Inputs + Outputs.
  • No API calls, no routing.
  • Easy to test + reuse.
// Dumb component: UI-only
export class OrderListComponent {
  orders = input<Order[]>([]);
  select = output<string>();
}
// Smart component: orchestration
export class OrdersPageComponent {
  orders$ = this.facade.orders$;
  onSelect(id: string) { this.facade.select(id); }
}

3. Dependency Injection (DI) & Providers

Senior Angular is about controlling scope: singletons, per-feature instances, and testable abstractions.

✅ Best practices

  • Put singletons in core only (auth, interceptors).
  • Provide feature-specific services at route/feature level.
  • Prefer facades for features (UI doesn't talk to HttpClient directly).

🚨 Traps

  • Service provided in root but used as feature state (shared across pages unexpectedly).
  • Injecting too much in components (hard to test).
// Route-level providers (great for feature-scoped state)
export const routes = [
  { path: 'orders', loadComponent: () => import('./orders.page'),
    providers: [OrdersFacade, OrdersApi] }
];

4. Modules vs Standalone (Modern Angular)

Standalone simplifies composition and tree-shaking. Modules still exist, but don’t need to dominate new codebases.

Standalone shines when

  • You want routes + lazy loading with fewer files.
  • You want explicit imports per component.
  • You want feature isolation (providers at route level).

Modules are ok when

  • You maintain legacy apps or library packaging.
  • You have shared UI libraries already built around NgModules.

5. RxJS Fundamentals (What interviewers really test)

✅ Key ideas

  • Observable is lazy (executes on subscription).
  • Operators build a pipeline (map/filter/switchMap).
  • Subscription is a resource (must be cleaned up).
  • Cold vs Hot streams matters for duplication.

🚨 Classic trap

Subscribing inside subscribe (nested subscriptions) → leaks + race conditions. Prefer higher-order mapping operators.

// ❌ BAD: nested subscribe
user$.subscribe(u => {
  http.get(`/api/orders?user=${u.id}`).subscribe(orders => {});
});

// ✅ GOOD: switchMap
orders$ = user$.pipe(
  switchMap(u => http.get(`/api/orders?user=${u.id}`))
);

6. Subjects (BehaviorSubject / ReplaySubject)

BehaviorSubject

Holds the latest value. New subscribers immediately receive the current value.

ReplaySubject

Replays N previous values. Useful for caching streams (careful with memory).

🚨 Trap: Subject everywhere

Subjects are powerful but can become “event spaghetti”. Prefer derived streams (pipe) and keep Subjects private inside facades.

7. Operators & Patterns (switchMap vs mergeMap vs concatMap)

switchMap

Cancels previous request. Perfect for typeahead / latest wins.

mergeMap

Runs in parallel. Use for independent work (limit concurrency if needed).

concatMap

Queues requests sequentially. Use when order matters.

// Typeahead pattern
results$ = query$.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(q => http.get(`/api/search?q=${q}`)),
  shareReplay({ bufferSize: 1, refCount: true })
);

8. HTTP Streams, Caching & shareReplay

🚨 Trap: multiple async pipes = multiple HTTP calls

If you bind the same cold HTTP observable multiple times, you may trigger multiple requests. Use caching (shareReplay) or a facade with a single shared stream.

// Cache last value and share across subscribers
orders$ = http.get<Order[]>('/api/orders').pipe(
  shareReplay({ bufferSize: 1, refCount: true })
);

⚠️ Note

shareReplay can keep data in memory. Prefer refCount=true and be intentional about caching boundaries.

9. Subscriptions & Memory Leaks

✅ Good patterns

  • Prefer async pipe in templates.
  • Use takeUntilDestroyed for manual subscriptions.
  • Keep subscriptions in containers, not in dumb components.

🚨 Traps

  • Subscribing in services without lifecycle control.
  • Forgetting to unsubscribe in long-lived components.
  • Subjects never completed + global event listeners.
// ✅ Angular modern cleanup
import { DestroyRef, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export class OrdersPageComponent {
  private destroyRef = inject(DestroyRef);

  ngOnInit() {
    events$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
  }
}

10. Change Detection (CD) & Zones

A senior Angular dev understands when the UI re-renders and how to control it.

💡 Key interview point

Default strategy checks many components frequently. If your data is immutable and streams-driven, OnPush reduces work.

11. OnPush Strategy (The performance default)

✅ When OnPush updates

  • Input reference changes (immutability).
  • Event in the component (click, input).
  • Async pipe emits a new value.

🚨 Trap: mutating arrays/objects

If you mutate in place, OnPush may not detect changes. Prefer new references (spread, map, etc.).

12. Signals & RxJS Interop

Signals are great for local UI state; RxJS remains excellent for async workflows and complex stream composition.

// Use signals for local UI state, RxJS for async workflows
// (interview-friendly statement)

13. State Management (Facade-first)

Facade pattern

Components consume readonly streams + call methods. Implementation (signals/RxJS/store) is hidden behind the facade.

Store choice (NgRx/Akita/Signals)

Pick based on team maturity and app complexity. Don’t choose a store “because trendy”.

// Facade sketch
export class OrdersFacade {
  orders$ = this.api.orders$;
  select(id: string) { /* update state */ }
}

🎯 Interview Advice

If you say “feature boundaries + OnPush + async pipe + switchMap + takeUntilDestroyed + facade-first state” — you’ll sound like a real senior Angular engineer.