Real-world Application Patterns

1. HTTP Request Management and Cancellation

Pattern Operator Description Use Case
Auto-Cancel Previous switchMap() Cancel previous request when new one starts Search, autocomplete, type-ahead
Prevent Duplicate exhaustMap() Ignore new requests while one is pending Save buttons, form submissions
Concurrent Requests mergeMap() Allow multiple simultaneous requests Batch operations, parallel loading
Sequential Requests concatMap() Wait for previous request to complete Ordered operations, dependencies
Retry with Backoff retryWhen() Retry failed requests with delay Network resilience
Polling interval() + switchMap() Periodic data refresh Real-time updates, status checks

Example: Search with auto-cancel and debounce

// Autocomplete search
const searchInput$ = fromEvent<Event>(searchInput, 'input').pipe(
  map(e => (e.target as HTMLInputElement).value),
  debounceTime(300),           // Wait for user to stop typing
  distinctUntilChanged(),      // Only if value changed
  filter(term => term.length >= 2),  // Min 2 characters
  tap(() => showLoadingSpinner()),
  switchMap(term =>            // Cancel previous searches
    this.http.get<Result[]>(`/api/search?q=${term}`).pipe(
      catchError(err => {
        console.error('Search failed:', err);
        return of([]);         // Return empty on error
      }),
      finalize(() => hideLoadingSpinner())
    )
  ),
  shareReplay({ bufferSize: 1, refCount: true })
);

searchInput$.subscribe(results => displayResults(results));

Example: Prevent duplicate submissions

// Save button that prevents duplicate clicks
const saveButton$ = fromEvent(saveBtn, 'click');

saveButton$.pipe(
  tap(() => saveBtn.disabled = true),
  exhaustMap(() =>               // Ignore clicks while saving
    this.http.post('/api/save', formData).pipe(
      timeout(10000),            // 10 second timeout
      retry(2),                  // Retry twice
      catchError(err => {
        showError('Save failed');
        return throwError(() => err);
      }),
      finalize(() => saveBtn.disabled = false)
    )
  )
).subscribe({
  next: response => showSuccess('Saved successfully'),
  error: err => console.error('Save error:', err)
});

Example: Parallel requests with concurrency limit

// Load user details for multiple IDs with max 5 concurrent
const userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

from(userIds).pipe(
  mergeMap(
    id => this.http.get<User>(`/api/users/${id}`).pipe(
      catchError(err => {
        console.error(`Failed to load user ${id}:`, err);
        return of(null);         // Continue with others
      })
    ),
    5                            // Max 5 concurrent requests
  ),
  filter(user => user !== null), // Remove failed requests
  toArray()                      // Collect all results
).subscribe(users => {
  console.log('Loaded users:', users);
  displayUsers(users);
});

Example: Polling with start/stop control

// Polling service with start/stop
class PollingService {
  private polling$ = new Subject<boolean>();
  
  startPolling(url: string, intervalMs: number = 5000): Observable<any> {
    return this.polling$.pipe(
      startWith(true),
      switchMap(shouldPoll => 
        shouldPoll 
          ? interval(intervalMs).pipe(
              startWith(0),      // Immediate first request
              switchMap(() => this.http.get(url).pipe(
                catchError(err => {
                  console.error('Poll failed:', err);
                  return of(null);
                })
              ))
            )
          : EMPTY                // Stop polling
      )
    );
  }
  
  stop() {
    this.polling$.next(false);
  }
  
  start() {
    this.polling$.next(true);
  }
}

// Usage
const poller = new PollingService();
const data$ = poller.startPolling('/api/status', 3000);

data$.subscribe(status => updateStatus(status));

// Stop polling when tab not visible
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    poller.stop();
  } else {
    poller.start();
  }
});

2. User Input Handling and Form Validation

Pattern Implementation Description Benefit
Debounced Validation debounceTime() + switchMap() Validate after user stops typing Reduce API calls
Cross-field Validation combineLatest() Validate multiple fields together Password confirmation, date ranges
Async Validators switchMap() to API Server-side validation (uniqueness) Username availability
Real-time Feedback map() + scan() Show validation as user types Password strength, character count
Form State BehaviorSubject Centralized form state management Complex multi-step forms

Example: Real-time form validation

// Email input with async validation
const emailInput$ = fromEvent<Event>(emailField, 'input').pipe(
  map(e => (e.target as HTMLInputElement).value),
  shareReplay({ bufferSize: 1, refCount: true })
);

// Synchronous validation
const emailValid$ = emailInput$.pipe(
  map(email => ({
    valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
    error: 'Invalid email format'
  }))
);

// Asynchronous validation (check availability)
const emailAvailable$ = emailInput$.pipe(
  debounceTime(500),
  distinctUntilChanged(),
  filter(email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)),
  switchMap(email => 
    this.http.get<{available: boolean}>(`/api/check-email?email=${email}`).pipe(
      map(result => ({
        valid: result.available,
        error: result.available ? null : 'Email already taken'
      })),
      catchError(() => of({ valid: true, error: null }))
    )
  ),
  startWith({ valid: true, error: null })
);

// Combine validations
combineLatest([emailValid$, emailAvailable$]).pipe(
  map(([format, available]) => {
    if (!format.valid) return format;
    if (!available.valid) return available;
    return { valid: true, error: null };
  })
).subscribe(validation => {
  if (validation.valid) {
    emailField.classList.remove('error');
    errorMsg.textContent = '';
  } else {
    emailField.classList.add('error');
    errorMsg.textContent = validation.error;
  }
});

Example: Password strength meter

// Password strength indicator
const passwordInput$ = fromEvent<Event>(passwordField, 'input').pipe(
  map(e => (e.target as HTMLInputElement).value),
  shareReplay({ bufferSize: 1, refCount: true })
);

interface PasswordStrength {
  score: number;      // 0-4
  label: string;      // weak, fair, good, strong
  requirements: {
    length: boolean;
    uppercase: boolean;
    lowercase: boolean;
    number: boolean;
    special: boolean;
  };
}

const passwordStrength$ = passwordInput$.pipe(
  map(password => {
    const requirements = {
      length: password.length >= 8,
      uppercase: /[A-Z]/.test(password),
      lowercase: /[a-z]/.test(password),
      number: /[0-9]/.test(password),
      special: /[^A-Za-z0-9]/.test(password)
    };
    
    const metCount = Object.values(requirements).filter(Boolean).length;
    
    let score = 0;
    let label = 'weak';
    
    if (metCount === 5) {
      score = 4;
      label = 'strong';
    } else if (metCount === 4) {
      score = 3;
      label = 'good';
    } else if (metCount === 3) {
      score = 2;
      label = 'fair';
    } else if (metCount >= 1) {
      score = 1;
      label = 'weak';
    }
    
    return { score, label, requirements };
  })
);

passwordStrength$.subscribe(strength => {
  updateStrengthMeter(strength.score);
  strengthLabel.textContent = strength.label;
  
  // Update requirement checklist
  Object.entries(strength.requirements).forEach(([req, met]) => {
    const element = document.getElementById(`req-${req}`);
    element.classList.toggle('met', met);
  });
});

Example: Multi-step form with state management

// Multi-step form manager
class FormWizard {
  private currentStep$ = new BehaviorSubject<number>(1);
  private formData$ = new BehaviorSubject<Partial<FormData>>({});
  
  public step$ = this.currentStep$.asObservable();
  public data$ = this.formData$.asObservable();
  
  // Validation status for each step
  public stepValid$ = (step: number): Observable<boolean> => {
    return this.formData$.pipe(
      map(data => this.validateStep(step, data))
    );
  };
  
  // Can navigate to next step
  public canGoNext$ = combineLatest([
    this.currentStep$,
    this.formData$
  ]).pipe(
    map(([step, data]) => this.validateStep(step, data))
  );
  
  updateData(stepData: Partial<FormData>) {
    this.formData$.next({
      ...this.formData$.value,
      ...stepData
    });
  }
  
  nextStep() {
    const current = this.currentStep$.value;
    if (this.validateStep(current, this.formData$.value)) {
      this.currentStep$.next(current + 1);
    }
  }
  
  previousStep() {
    const current = this.currentStep$.value;
    if (current > 1) {
      this.currentStep$.next(current - 1);
    }
  }
  
  submit(): Observable<any> {
    return this.http.post('/api/submit', this.formData$.value);
  }
  
  private validateStep(step: number, data: Partial<FormData>): boolean {
    // Validation logic per step
    switch(step) {
      case 1:
        return !!data.name && !!data.email;
      case 2:
        return !!data.address && !!data.city;
      case 3:
        return !!data.payment;
      default:
        return false;
    }
  }
}

// Usage
const wizard = new FormWizard();

wizard.canGoNext$.subscribe(canGoNext => {
  nextBtn.disabled = !canGoNext;
});

wizard.step$.subscribe(step => {
  showStep(step);
});

3. Real-time Data Synchronization

Pattern Implementation Description Use Case
Optimistic Updates Update UI, then sync Immediate feedback, rollback on error Todo apps, social media
Conflict Resolution Merge strategies Handle concurrent edits Collaborative editing
Delta Sync Send only changes Minimize bandwidth Large datasets
Offline Queue Queue operations, sync when online Offline-first apps Mobile apps, PWAs
Version Control Track versions, detect conflicts Consistency guarantees Documents, configuration

Example: Optimistic update pattern

// Todo service with optimistic updates
class TodoService {
  private todos$ = new BehaviorSubject<Todo[]>([]);
  
  getTodos(): Observable<Todo[]> {
    return this.todos$.asObservable();
  }
  
  addTodo(text: string): Observable<Todo> {
    // Create optimistic todo
    const optimisticTodo: Todo = {
      id: `temp-${Date.now()}`,
      text,
      completed: false,
      synced: false
    };
    
    // Update UI immediately
    this.todos$.next([...this.todos$.value, optimisticTodo]);
    
    // Sync to server
    return this.http.post<Todo>('/api/todos', { text }).pipe(
      tap(serverTodo => {
        // Replace optimistic with server response
        const todos = this.todos$.value.map(t => 
          t.id === optimisticTodo.id 
            ? { ...serverTodo, synced: true }
            : t
        );
        this.todos$.next(todos);
      }),
      catchError(err => {
        // Rollback on error
        const todos = this.todos$.value.filter(t => t.id !== optimisticTodo.id);
        this.todos$.next(todos);
        return throwError(() => err);
      })
    );
  }
  
  updateTodo(id: string, updates: Partial<Todo>): Observable<Todo> {
    // Optimistic update
    const todos = this.todos$.value.map(t => 
      t.id === id ? { ...t, ...updates, synced: false } : t
    );
    this.todos$.next(todos);
    
    // Sync to server
    return this.http.put<Todo>(`/api/todos/${id}`, updates).pipe(
      tap(serverTodo => {
        const updated = this.todos$.value.map(t => 
          t.id === id ? { ...serverTodo, synced: true } : t
        );
        this.todos$.next(updated);
      }),
      catchError(err => {
        // Revert on error
        this.loadTodos().subscribe();
        return throwError(() => err);
      })
    );
  }
  
  private loadTodos(): Observable<Todo[]> {
    return this.http.get<Todo[]>('/api/todos').pipe(
      tap(todos => this.todos$.next(todos.map(t => ({ ...t, synced: true }))))
    );
  }
}

Example: Offline-first with sync queue

// Offline sync manager
class OfflineSyncService {
  private syncQueue$ = new BehaviorSubject<SyncOperation[]>([]);
  private online$ = merge(
    fromEvent(window, 'online').pipe(mapTo(true)),
    fromEvent(window, 'offline').pipe(mapTo(false))
  ).pipe(
    startWith(navigator.onLine),
    shareReplay({ bufferSize: 1, refCount: true })
  );
  
  // Sync when online
  constructor() {
    this.online$.pipe(
      filter(online => online),
      switchMap(() => this.processSyncQueue())
    ).subscribe();
  }
  
  queueOperation(operation: SyncOperation) {
    const queue = [...this.syncQueue$.value, operation];
    this.syncQueue$.next(queue);
    
    // Save to localStorage for persistence
    localStorage.setItem('syncQueue', JSON.stringify(queue));
    
    // Try to sync if online
    if (navigator.onLine) {
      this.processSyncQueue().subscribe();
    }
  }
  
  private processSyncQueue(): Observable<void> {
    const queue = this.syncQueue$.value;
    
    if (queue.length === 0) {
      return of(undefined);
    }
    
    return from(queue).pipe(
      concatMap(operation => 
        this.executeOperation(operation).pipe(
          tap(() => this.removeFromQueue(operation)),
          catchError(err => {
            console.error('Sync failed:', err);
            // Keep in queue, try again later
            return of(undefined);
          })
        )
      ),
      toArray(),
      map(() => undefined)
    );
  }
  
  private executeOperation(op: SyncOperation): Observable<any> {
    switch (op.type) {
      case 'CREATE':
        return this.http.post(op.url, op.data);
      case 'UPDATE':
        return this.http.put(op.url, op.data);
      case 'DELETE':
        return this.http.delete(op.url);
      default:
        return throwError(() => new Error('Unknown operation'));
    }
  }
  
  private removeFromQueue(operation: SyncOperation) {
    const queue = this.syncQueue$.value.filter(op => op.id !== operation.id);
    this.syncQueue$.next(queue);
    localStorage.setItem('syncQueue', JSON.stringify(queue));
  }
  
  getQueueStatus(): Observable<{ pending: number; online: boolean }> {
    return combineLatest([
      this.syncQueue$,
      this.online$
    ]).pipe(
      map(([queue, online]) => ({
        pending: queue.length,
        online
      }))
    );
  }
}

// Usage
const syncService = new OfflineSyncService();

// Queue operation
syncService.queueOperation({
  id: Date.now().toString(),
  type: 'CREATE',
  url: '/api/todos',
  data: { text: 'New todo' }
});

// Monitor sync status
syncService.getQueueStatus().subscribe(status => {
  if (status.pending > 0) {
    showSyncIndicator(`${status.pending} items pending`);
  }
});

4. WebSocket Message Processing

Pattern Implementation Description Use Case
Message Routing filter() by message type Route messages to handlers Chat, notifications, updates
Request-Response correlationId matching Match responses to requests RPC over WebSocket
Message Ordering concatMap() or sequence numbers Ensure message order Critical sequences
Heartbeat interval() + merge() Keep connection alive Connection monitoring
Reconnect Buffer Buffer during disconnect Queue messages while offline Guaranteed delivery

Example: WebSocket message router

// Advanced WebSocket message router
class WebSocketRouter {
  private ws$: WebSocketSubject<any>;
  private messageRoutes = new Map<string, Subject<any>>();
  
  constructor(url: string) {
    this.ws$ = webSocket({
      url,
      openObserver: { next: () => console.log('WS Connected') },
      closeObserver: { next: () => console.log('WS Disconnected') }
    });
    
    // Route incoming messages
    this.ws$.pipe(
      retryWhen(errors => errors.pipe(delay(5000)))
    ).subscribe(message => {
      const route = this.messageRoutes.get(message.type);
      if (route) {
        route.next(message.data);
      } else {
        console.warn('Unhandled message type:', message.type);
      }
    });
  }
  
  // Subscribe to specific message type
  on<T>(messageType: string): Observable<T> {
    if (!this.messageRoutes.has(messageType)) {
      this.messageRoutes.set(messageType, new Subject<T>());
    }
    return this.messageRoutes.get(messageType)!.asObservable();
  }
  
  // Send message
  send(type: string, data: any) {
    this.ws$.next({ type, data, timestamp: Date.now() });
  }
  
  // Request-response pattern
  request<T>(type: string, data: any, timeout = 5000): Observable<T> {
    const correlationId = `req-${Date.now()}-${Math.random()}`;
    
    // Wait for response with matching correlationId
    const response$ = this.ws$.pipe(
      filter((msg: any) => 
        msg.type === `${type}-response` && 
        msg.correlationId === correlationId
      ),
      map((msg: any) => msg.data as T),
      take(1),
      timeout(timeout)
    );
    
    // Send request
    this.ws$.next({ 
      type, 
      data, 
      correlationId,
      timestamp: Date.now() 
    });
    
    return response$;
  }
}

// Usage
const wsRouter = new WebSocketRouter('ws://localhost:8080');

// Subscribe to chat messages
wsRouter.on<ChatMessage>('chat').subscribe(
  msg => displayChatMessage(msg)
);

// Subscribe to notifications
wsRouter.on<Notification>('notification').subscribe(
  notif => showNotification(notif)
);

// Request-response
wsRouter.request<UserProfile>('get-profile', { userId: 123 })
  .subscribe({
    next: profile => console.log('Profile:', profile),
    error: err => console.error('Request timeout or error:', err)
  });

5. Animation and UI State Management

Pattern Implementation Description Use Case
RAF Animation animationFrameScheduler Smooth 60fps animations Scroll effects, transitions
Tween Animation interval() + map() Value interpolation over time Number counters, progress bars
Gesture Handling merge() mouse/touch events Unified gesture processing Drag, swipe, pinch
State Machine scan() + BehaviorSubject Manage complex UI states Modal flows, wizards
Loading States startWith() + catchError() Track async operation status Spinners, skeletons

Example: Smooth scroll animation

// Smooth scroll to element
function smoothScrollTo(element: HTMLElement, duration = 1000): Observable<number> {
  const start = window.pageYOffset;
  const target = element.offsetTop;
  const distance = target - start;
  const startTime = performance.now();
  
  return interval(0, animationFrameScheduler).pipe(
    map(() => (performance.now() - startTime) / duration),
    takeWhile(progress => progress < 1),
    map(progress => {
      // Easing function (ease-in-out)
      const eased = progress < 0.5
        ? 2 * progress * progress
        : -1 + (4 - 2 * progress) * progress;
      return start + (distance * eased);
    }),
    tap(position => window.scrollTo(0, position)),
    endWith(target)
  );
}

// Usage
const scrollBtn = document.getElementById('scroll-btn');
const targetSection = document.getElementById('target');

fromEvent(scrollBtn, 'click').pipe(
  switchMap(() => smoothScrollTo(targetSection, 800))
).subscribe({
  complete: () => console.log('Scroll complete')
});

Example: Drag and drop with RxJS

// Draggable element
function makeDraggable(element: HTMLElement): Observable<{x: number; y: number}> {
  const mouseDown$ = fromEvent<MouseEvent>(element, 'mousedown');
  const mouseMove$ = fromEvent<MouseEvent>(document, 'mousemove');
  const mouseUp$ = fromEvent<MouseEvent>(document, 'mouseup');
  
  return mouseDown$.pipe(
    switchMap(start => {
      const startX = start.clientX - element.offsetLeft;
      const startY = start.clientY - element.offsetTop;
      
      return mouseMove$.pipe(
        map(move => ({
          x: move.clientX - startX,
          y: move.clientY - startY
        })),
        takeUntil(mouseUp$)
      );
    })
  );
}

// Usage
const draggable = document.getElementById('draggable');

makeDraggable(draggable).subscribe(pos => {
  draggable.style.left = `${pos.x}px`;
  draggable.style.top = `${pos.y}px`;
});

Example: UI state machine

// Modal state machine
type ModalState = 'closed' | 'opening' | 'open' | 'closing';
type ModalAction = 'OPEN' | 'CLOSE' | 'TRANSITION_COMPLETE';

class ModalStateMachine {
  private actions$ = new Subject<ModalAction>();
  
  public state$ = this.actions$.pipe(
    scan((state: ModalState, action: ModalAction) => {
      switch (state) {
        case 'closed':
          return action === 'OPEN' ? 'opening' : state;
        case 'opening':
          return action === 'TRANSITION_COMPLETE' ? 'open' : state;
        case 'open':
          return action === 'CLOSE' ? 'closing' : state;
        case 'closing':
          return action === 'TRANSITION_COMPLETE' ? 'closed' : state;
        default:
          return state;
      }
    }, 'closed' as ModalState),
    startWith('closed' as ModalState),
    shareReplay({ bufferSize: 1, refCount: true })
  );
  
  open() {
    this.actions$.next('OPEN');
    
    // Auto-complete transition after animation
    timer(300).subscribe(() => {
      this.actions$.next('TRANSITION_COMPLETE');
    });
  }
  
  close() {
    this.actions$.next('CLOSE');
    
    timer(300).subscribe(() => {
      this.actions$.next('TRANSITION_COMPLETE');
    });
  }
}

// Usage
const modal = new ModalStateMachine();

modal.state$.subscribe(state => {
  const element = document.getElementById('modal');
  
  element.className = `modal modal-${state}`;
  
  if (state === 'open') {
    element.setAttribute('aria-hidden', 'false');
  } else if (state === 'closed') {
    element.setAttribute('aria-hidden', 'true');
  }
});

openBtn.addEventListener('click', () => modal.open());
closeBtn.addEventListener('click', () => modal.close());

6. Complex Workflow Orchestration

Pattern Implementation Description Use Case
Sequential Workflow concatMap() Execute steps in order Onboarding, checkout
Parallel with Merge forkJoin() Wait for all parallel tasks Data aggregation
Conditional Branching iif() or switchMap Dynamic workflow paths A/B testing, permissions
Saga Pattern Compensating transactions Rollback on failure Distributed transactions
Circuit Breaker Error tracking + fallback Prevent cascade failures Microservices

Example: Multi-step checkout workflow

// Complex checkout workflow
class CheckoutWorkflow {
  executeCheckout(order: Order): Observable<CheckoutResult> {
    return of(order).pipe(
      // Step 1: Validate cart
      concatMap(order => this.validateCart(order).pipe(
        tap(() => this.updateProgress('Validating cart...'))
      )),
      
      // Step 2: Check inventory
      concatMap(order => this.checkInventory(order).pipe(
        tap(() => this.updateProgress('Checking inventory...'))
      )),
      
      // Step 3: Calculate totals
      concatMap(order => this.calculateTotals(order).pipe(
        tap(() => this.updateProgress('Calculating totals...'))
      )),
      
      // Step 4: Process payment
      concatMap(order => this.processPayment(order).pipe(
        tap(() => this.updateProgress('Processing payment...'))
      )),
      
      // Step 5: Reserve inventory
      concatMap(order => this.reserveInventory(order).pipe(
        tap(() => this.updateProgress('Reserving items...'))
      )),
      
      // Step 6: Create order
      concatMap(order => this.createOrder(order).pipe(
        tap(() => this.updateProgress('Creating order...'))
      )),
      
      // Step 7: Send confirmation
      concatMap(result => this.sendConfirmation(result).pipe(
        map(() => result),
        tap(() => this.updateProgress('Complete!'))
      )),
      
      // Error handling with rollback
      catchError(err => {
        console.error('Checkout failed:', err);
        return this.rollbackCheckout(order).pipe(
          concatMap(() => throwError(() => err))
        );
      })
    );
  }
  
  private rollbackCheckout(order: Order): Observable<void> {
    // Compensating transactions
    return forkJoin([
      this.releaseInventory(order).pipe(catchError(() => of(null))),
      this.refundPayment(order).pipe(catchError(() => of(null))),
      this.cancelOrder(order).pipe(catchError(() => of(null)))
    ]).pipe(
      map(() => undefined),
      tap(() => console.log('Rollback complete'))
    );
  }
}

// Usage
const checkout = new CheckoutWorkflow();

checkout.executeCheckout(order).subscribe({
  next: result => {
    showSuccess(`Order #${result.orderId} confirmed!`);
    redirectToConfirmation(result.orderId);
  },
  error: err => {
    showError('Checkout failed. Please try again.');
    console.error(err);
  }
});

Example: Circuit breaker pattern

// Circuit breaker for resilient API calls
class CircuitBreaker {
  private failureCount = 0;
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  private failureThreshold = 5;
  private resetTimeout = 60000; // 1 minute
  
  execute<T>(request: Observable<T>): Observable<T> {
    if (this.state === 'OPEN') {
      return throwError(() => new Error('Circuit breaker is OPEN'));
    }
    
    return request.pipe(
      tap(() => {
        // Success - reset failure count
        this.failureCount = 0;
        if (this.state === 'HALF_OPEN') {
          this.state = 'CLOSED';
          console.log('Circuit breaker CLOSED');
        }
      }),
      catchError(err => {
        this.failureCount++;
        
        if (this.failureCount >= this.failureThreshold) {
          this.state = 'OPEN';
          console.log('Circuit breaker OPEN');
          
          // Auto-retry after timeout
          timer(this.resetTimeout).subscribe(() => {
            this.state = 'HALF_OPEN';
            this.failureCount = 0;
            console.log('Circuit breaker HALF_OPEN');
          });
        }
        
        return throwError(() => err);
      })
    );
  }
  
  getState(): string {
    return this.state;
  }
}

// Usage
const breaker = new CircuitBreaker();

function makeApiCall(): Observable<any> {
  return breaker.execute(
    this.http.get('/api/unstable-endpoint').pipe(
      timeout(5000)
    )
  ).pipe(
    catchError(err => {
      if (err.message === 'Circuit breaker is OPEN') {
        // Use cached data or show user-friendly message
        return of({ cached: true, data: getCachedData() });
      }
      return throwError(() => err);
    })
  );
}

// Monitor circuit breaker state
interval(1000).subscribe(() => {
  const state = breaker.getState();
  updateStatusIndicator(state);
});
Real-world Pattern Best Practices:
  • Request management: Use switchMap for searches, exhaustMap for submissions
  • Form validation: Combine sync and async validators with debouncing
  • Data sync: Implement optimistic updates with rollback on error
  • WebSocket: Add message routing, correlation IDs, and reconnection logic
  • Animations: Use animationFrameScheduler for smooth 60fps performance
  • Workflows: Design compensating transactions for failure recovery

Section 17 Summary

  • HTTP management - switchMap cancels, exhaustMap prevents duplicates, retryWhen adds resilience
  • Form validation - combine debouncing, async validation, and cross-field checks
  • Data sync - optimistic updates with rollback, offline queues for resilience
  • WebSocket processing - message routing, request-response patterns, reconnection
  • UI animations - animationFrameScheduler for smooth effects, state machines for flows
  • Workflow orchestration - sequential steps, parallel execution, circuit breakers