Skip to content

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 switchMap vs mergeMap vs concatMap?
  • What is a Subject? BehaviorSubject vs ReplaySubject?
  • How do you prevent memory leaks from subscriptions?
  • What is the async pipe?
  • What is NgRx? Explain the Redux pattern.
  • What are Actions, Reducers, Selectors, Effects?
  • When would you use state management?
  • What is debounceTime and distinctUntilChanged?