Hot Observable vs Cold Observable
Cold Observable
Definition: Cold Observable is lazy, each subscriber independently executes the Observable logic.
Characteristics:
- Each subscriber gets an independent data stream
- Execution starts when subscribed
- Does not share data
- Producer does not actively push data
Example:
javascriptimport { Observable } from 'rxjs'; const cold$ = new Observable(subscriber => { console.log('Observable executed'); subscriber.next(Math.random()); subscriber.complete(); }); cold$.subscribe(value => console.log('Subscriber 1:', value)); // Observable executed // Subscriber 1: 0.123456 cold$.subscribe(value => console.log('Subscriber 2:', value)); // Observable executed // Subscriber 2: 0.789012 // Note: Each subscription re-executes, producing different random numbers
Common Cold Observables:
of()from()interval()timer()ajax()http.get()(Angular)- Most creation operators
Hot Observable
Definition: Hot Observable is active, multiple subscribers share the same data stream.
Characteristics:
- All subscribers share the same data stream
- Executes even without subscribers
- Shares data
- Producer actively pushes data
Example:
javascriptimport { Observable, Subject } from 'rxjs'; const hot$ = new Observable(subscriber => { console.log('Observable executed'); subscriber.next(Math.random()); subscriber.complete(); }); const subject = new Subject(); hot$.subscribe(subject); subject.subscribe(value => console.log('Subscriber 1:', value)); // Observable executed // Subscriber 1: 0.123456 subject.subscribe(value => console.log('Subscriber 2:', value)); // Subscriber 2: 0.123456 // Note: Both subscribers receive the same value
Common Hot Observables:
Subjectand its variantsBehaviorSubjectReplaySubjectAsyncSubject- DOM events (via
fromEvent) - WebSocket connections
- Observable converted with
share()
Conversion Methods
1. Using share() to Convert Cold to Hot
javascriptimport { interval } from 'rxjs'; import { share, take } from 'rxjs/operators'; const cold$ = interval(1000).pipe( take(5) ); const hot$ = cold$.pipe( share() // Convert to Hot Observable ); hot$.subscribe(value => console.log('Subscriber 1:', value)); hot$.subscribe(value => console.log('Subscriber 2:', value)); // Both subscribers share the same data stream
2. Using shareReplay() to Cache Values
javascriptimport { interval } from 'rxjs'; import { shareReplay, take } from 'rxjs/operators'; const hot$ = interval(1000).pipe( take(5), shareReplay(1) // Cache the last value ); hot$.subscribe(value => console.log('Subscriber 1:', value)); setTimeout(() => { hot$.subscribe(value => console.log('Subscriber 2:', value)); // New subscriber immediately receives cached value }, 3000);
3. Using publish() and connect()
javascriptimport { interval } from 'rxjs'; import { publish, take } from 'rxjs/operators'; const cold$ = interval(1000).pipe( take(5) ); const hot$ = cold$.pipe( publish() // Convert to Hot Observable ); hot$.subscribe(value => console.log('Subscriber 1:', value)); hot$.subscribe(value => console.log('Subscriber 2:', value)); hot$.connect(); // Start execution
Practical Use Cases
Cold Observable Use Cases
- HTTP Requests
javascript// Each subscription initiates a new request http.get('/api/data').subscribe(data => { console.log('Request 1:', data); }); http.get('/api/data').subscribe(data => { console.log('Request 2:', data); });
- Independent Data Processing
javascript// Each subscriber needs independent data stream of(1, 2, 3).pipe( map(x => x * 2) ).subscribe(value => console.log(value));
- Scenarios Requiring Re-execution
javascript// Recalculate on each subscription const calculation$ = new Observable(subscriber => { const result = expensiveCalculation(); subscriber.next(result); subscriber.complete(); });
Hot Observable Use Cases
- Shared Data
javascript// Multiple components share the same data stream const userData$ = http.get('/api/user').pipe( share() ); component1.userData$.subscribe(user => { console.log('Component 1:', user); }); component2.userData$.subscribe(user => { console.log('Component 2:', user); }); // Only one request initiated, both components share the result
- Event Streams
javascript// Multiple subscribers listen to the same event const click$ = fromEvent(document, 'click').pipe( share() ); click$.subscribe(event => { console.log('Handler 1:', event); }); click$.subscribe(event => { console.log('Handler 2:', event); });
- WebSocket Connections
javascript// Multiple subscribers share the same WebSocket connection const socket$ = webSocket('ws://localhost:8080').pipe( share() ); socket$.subscribe(message => { console.log('Handler 1:', message); }); socket$.subscribe(message => { console.log('Handler 2:', message); });
Performance Comparison
Cold Observable Performance Characteristics
Advantages:
- Each subscriber gets independent data stream
- No interference between subscribers
- Suitable for scenarios requiring independent processing
Disadvantages:
- May repeatedly execute the same operations
- Wastes resources (e.g., duplicate HTTP requests)
- Higher memory usage
Hot Observable Performance Characteristics
Advantages:
- Shared data stream, avoids repeated execution
- Saves resources (e.g., only one HTTP request)
- Lower memory usage
Disadvantages:
- Subscribers may miss previous data
- Need to manage subscription timing
- Possible race conditions
Selection Guide
Use Cold Observable when:
- Each subscriber needs independent data stream
- Need to re-execute operations
- Subscribers should not affect each other
- Data source is generated on demand
Use Hot Observable when:
- Multiple subscribers need to share data
- Need to avoid repeated execution (e.g., HTTP requests)
- Data source is actively pushed (e.g., events, WebSocket)
- Need to cache data for future subscribers
Best Practices
1. HTTP Request Sharing
javascript// ❌ Wrong: Each subscription initiates a request class UserService { getUser(id: string) { return http.get(`/api/users/${id}`); } } // ✅ Correct: Share request results class UserService { private cache = new Map<string, Observable<User>>(); getUser(id: string) { if (!this.cache.has(id)) { this.cache.set(id, http.get(`/api/users/${id}`).pipe( shareReplay(1) )); } return this.cache.get(id)!; } }
2. Event Handling
javascript// Use share() to share event stream const resize$ = fromEvent(window, 'resize').pipe( debounceTime(200), share() ); resize$.subscribe(event => { updateLayout1(event); }); resize$.subscribe(event => { updateLayout2(event); });
3. State Management
javascript// Use BehaviorSubject to manage state const state$ = new BehaviorSubject(initialState); state$.subscribe(state => { console.log('Listener 1:', state); }); state$.subscribe(state => { console.log('Listener 2:', state); }); // Update state state$.next(newState);
Common Pitfalls
1. Forgetting to Share Causes Duplicate Requests
javascript// ❌ Wrong example const data$ = http.get('/api/data'); data$.subscribe(data => console.log('Component 1:', data)); data$.subscribe(data => console.log('Component 2:', data)); // Two requests initiated // ✅ Correct example const data$ = http.get('/api/data').pipe( share() ); data$.subscribe(data => console.log('Component 1:', data)); data$.subscribe(data => console.log('Component 2:', data)); // Only one request initiated
2. Wrong Sharing Timing
javascript// ❌ Wrong example const data$ = http.get('/api/data').pipe( share() ); // Immediate subscription triggers request data$.subscribe(); // Later subscribers may miss data setTimeout(() => { data$.subscribe(data => console.log(data)); }, 2000); // ✅ Correct example const data$ = http.get('/api/data').pipe( shareReplay(1) // Cache data );
3. Improper Use of shareReplay
javascript// ❌ Wrong example: Caching too much data const data$ = interval(1000).pipe( shareReplay(1000) // Cache 1000 values, uses a lot of memory ); // ✅ Correct example: Reasonable cache size const data$ = interval(1000).pipe( shareReplay(1) // Only cache the last value );
Summary
Understanding the difference between Hot and Cold Observables is crucial for writing efficient RxJS code:
- Cold Observable: Lazy, independent execution, suitable for on-demand generated data
- Hot Observable: Active, shared execution, suitable for actively pushed data
- Conversion Methods: Use operators like
share(),shareReplay()for conversion - Performance Considerations: Hot Observable can avoid repeated execution, improving performance
- Selection Principle: Choose appropriate type based on scenario, avoid unnecessary resource waste
Proper use of these two Observable types can significantly improve application performance and maintainability.