RxJS & State Management¶
RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using Observables. Angular uses it everywhere — HTTP calls, forms, routing. Key concepts: Observable (data stream), Observer (consumer), Operators (transform/filter), Subscription. State management with ngrx/ngxs provides predictable state via Redux pattern.
Observables¶
An Observable is a lazy data stream that emits values over time. Unlike Promises (single value), Observables can emit multiple values and are cancellable. You must subscribe to receive values. Always unsubscribe to prevent memory leaks — or use the async pipe which handles it automatically.
Deep Dive: Observable vs Promise
// Creating an Observable
const numbers$ = new Observable<number>(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
subscriber.complete();
});
// Subscribing
numbers$.subscribe({
next: val => console.log(val),
error: err => console.error(err),
complete: () => console.log('Done')
});
// HTTP example — returns Observable
this.http.get<User[]>('/api/users').subscribe(users => {
this.users = users;
});
| Feature | Promise | Observable |
|---|---|---|
| Values | Single | Multiple |
| Lazy | No (executes immediately) | Yes (executes on subscribe) |
| Cancelable | No | Yes (unsubscribe) |
| Operators | .then() chain |
pipe() with operators |
Key Operators¶
Operators transform streams via pipe(). map — transform each value. filter — keep matching values. switchMap — cancel previous, switch to new Observable (use for HTTP). mergeMap — run in parallel. debounceTime — wait before emitting (search). distinctUntilChanged — skip duplicates. catchError — handle errors.
Deep Dive: Common Operators
import { map, filter, switchMap, debounceTime, distinctUntilChanged, catchError } from 'rxjs/operators';
// map — transform values
this.http.get<User[]>('/api/users').pipe(
map(users => users.filter(u => u.active))
);
// switchMap — cancel previous request (typeahead search)
this.searchControl.valueChanges.pipe(
debounceTime(300), // wait 300ms after last keystroke
distinctUntilChanged(), // skip if same value
switchMap(term => this.searchService.search(term)) // cancel previous
).subscribe(results => this.results = results);
// catchError — handle errors
this.http.get<User>('/api/users/1').pipe(
catchError(err => {
console.error('Failed:', err);
return of(null); // return fallback value
})
);
// combineLatest — combine multiple streams
combineLatest([this.users$, this.filter$]).pipe(
map(([users, filter]) => users.filter(u => u.role === filter))
);
| Operator | Purpose | Use Case |
|---|---|---|
map |
Transform value | Convert response data |
filter |
Keep matching | Filter stream values |
switchMap |
Switch to new Observable | HTTP on input change |
mergeMap |
Parallel Observables | Multiple simultaneous requests |
concatMap |
Sequential Observables | Ordered processing |
debounceTime |
Delay emission | Search input |
distinctUntilChanged |
Skip duplicates | Avoid redundant calls |
takeUntil |
Auto-unsubscribe | Component cleanup |
catchError |
Handle errors | HTTP error handling |
Subjects¶
A Subject is both an Observable and an Observer — it can emit values AND be subscribed to. Types: Subject (no initial value, no replay), BehaviorSubject (has initial value, emits latest to new subscribers), ReplaySubject (replays N values to new subscribers). Use BehaviorSubject for state that components need on subscribe.
Deep Dive: Types & Usage
// Subject — no initial value
const subject = new Subject<string>();
subject.subscribe(val => console.log('A:', val));
subject.next('Hello'); // A: Hello
subject.subscribe(val => console.log('B:', val));
subject.next('World'); // A: World, B: World (B missed "Hello")
// BehaviorSubject — has initial value, emits latest
const behavior = new BehaviorSubject<string>('initial');
behavior.subscribe(val => console.log('A:', val)); // A: initial
behavior.next('updated'); // A: updated
behavior.subscribe(val => console.log('B:', val)); // B: updated (gets latest)
// Service using BehaviorSubject for shared state
@Injectable({ providedIn: 'root' })
export class UserStateService {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable(); // expose as Observable (read-only)
setUser(user: User) {
this.userSubject.next(user);
}
}
Unsubscribing (Memory Leaks)¶
Forgetting to unsubscribe causes memory leaks. Solutions: 1) async pipe in template (auto-unsubscribes), 2) takeUntil with a destroy Subject, 3) Manual unsubscribe() in ngOnDestroy. The async pipe is the cleanest approach.
Deep Dive: Patterns
// ✅ Best: async pipe (auto-handles subscription)
// component.ts
users$ = this.userService.getUsers();
// template
// <div *ngFor="let user of users$ | async">{{ user.name }}</div>
// ✅ Good: takeUntil pattern
export class UserComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
this.userService.getUsers().pipe(
takeUntil(this.destroy$) // auto-unsubscribes
).subscribe(users => this.users = users);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
// ⚠️ Manual — works but error-prone
private subscription!: Subscription;
ngOnInit() {
this.subscription = this.data$.subscribe(...);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
NgRx (State Management)¶
NgRx implements the Redux pattern for Angular: Store (single state tree), Actions (events), Reducers (pure functions that update state), Selectors (query state), Effects (side effects like HTTP). Provides predictable state, time-travel debugging. Use for complex apps with shared state. NGXS is a simpler alternative.
Deep Dive: Architecture & Example
Component → dispatches Action → Reducer updates Store
↓
Component ← Selector reads ← Store (state)
↑
Effect (HTTP) → dispatches Action
// Actions
export const loadUsers = createAction('[Users] Load Users');
export const loadUsersSuccess = createAction('[Users] Load Success',
props<{ users: User[] }>()
);
// Reducer
export const userReducer = createReducer(
initialState,
on(loadUsers, state => ({ ...state, loading: true })),
on(loadUsersSuccess, (state, { users }) => ({
...state, users, loading: false
}))
);
// Selector
export const selectUsers = createSelector(
selectUserState,
state => state.users
);
// Effect (side effect — HTTP call)
@Injectable()
export class UserEffects {
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
switchMap(() => this.userService.getUsers().pipe(
map(users => loadUsersSuccess({ users }))
))
)
);
constructor(private actions$: Actions, private userService: UserService) {}
}
// Component
export class UsersComponent {
users$ = this.store.select(selectUsers);
constructor(private store: Store) {
this.store.dispatch(loadUsers());
}
}
| NgRx | NGXS |
|---|---|
| More boilerplate | Less boilerplate |
| Closer to Redux | More Angular-like |
| Actions, Reducers, Effects | State, Actions, Selectors |
| Industry standard | Simpler alternative |
Common Interview Questions¶
Common Interview Questions
- What is an Observable? How is it different from a Promise?
- What is
switchMapvsmergeMapvsconcatMap? - What is a Subject? BehaviorSubject vs ReplaySubject?
- How do you prevent memory leaks from subscriptions?
- What is the
asyncpipe? - What is NgRx? Explain the Redux pattern.
- What are Actions, Reducers, Selectors, Effects?
- When would you use state management?
- What is
debounceTimeanddistinctUntilChanged?