combineLatest
Combines multiple Observables to create an Observable whose values are calculated from the latest values of each of its input Observables.
Import
import { combineLatest } from 'rxjs' ;
Type Signature
// Array of Observables
function combineLatest < A extends readonly unknown []>(
sources : readonly [ ... ObservableInputTuple < A >]
) : Observable < A >;
// Dictionary of Observables
function combineLatest < T extends Record < string , ObservableInput < any >>>(
sourcesObject : T
) : Observable <{ [ K in keyof T ] : ObservedValueOf < T [ K ]> }>;
// With result selector
function combineLatest < A extends readonly unknown [], R >(
sources : readonly [ ... ObservableInputTuple < A >],
resultSelector : ( ... values : A ) => R
) : Observable < R >;
Parameters
sources
Array<ObservableInput> | Object
required
Either an array of Observables or an object where values are Observables.
Optional function to transform the combined values before emission.
Returns
An Observable that emits:
An array of latest values (when given an array)
An object of latest values (when given an object)
The result of resultSelector (when provided)
Description
combineLatest combines values from multiple Observables. Whenever any input Observable emits, it:
Collects the latest value from each Observable
Combines them into an array or object
Emits the combined result
Key characteristics:
Waits for ALL Observables to emit at least once before emitting
Emits every time ANY Observable emits (after initial values)
Maintains the latest value from each Observable
Completes only when ALL Observables complete
Examples
Combine Two Timers
import { timer , combineLatest } from 'rxjs' ;
const firstTimer = timer ( 0 , 1000 ); // 0, 1, 2, 3...
const secondTimer = timer ( 500 , 1000 ); // 0, 1, 2, 3... (starts 500ms later)
const combined = combineLatest ([ firstTimer , secondTimer ]);
combined . subscribe (([ first , second ]) => {
console . log ( `First: ${ first } , Second: ${ second } ` );
});
// Output:
// First: 0, Second: 0 (at 500ms)
// First: 1, Second: 0 (at 1000ms)
// First: 1, Second: 1 (at 1500ms)
// First: 2, Second: 1 (at 2000ms)
// ...
Combine Dictionary of Observables
import { of , delay , startWith , combineLatest } from 'rxjs' ;
const observables = {
a: of ( 1 ). pipe ( delay ( 1000 ), startWith ( 0 )),
b: of ( 5 ). pipe ( delay ( 5000 ), startWith ( 0 )),
c: of ( 10 ). pipe ( delay ( 10000 ), startWith ( 0 ))
};
const combined = combineLatest ( observables );
combined . subscribe ( value => console . log ( value ));
// Output:
// { a: 0, b: 0, c: 0 } (immediately)
// { a: 1, b: 0, c: 0 } (after 1s)
// { a: 1, b: 5, c: 0 } (after 5s)
// { a: 1, b: 5, c: 10 } (after 10s)
Calculate BMI from Weight and Height
import { of , combineLatest , map } from 'rxjs' ;
const weight$ = of ( 70 , 72 , 76 , 79 , 75 );
const height$ = of ( 1.76 , 1.77 , 1.78 );
const bmi$ = combineLatest ([ weight$ , height$ ]). pipe (
map (([ w , h ]) => w / ( h * h ))
);
bmi$ . subscribe ( x => console . log ( 'BMI:' , x . toFixed ( 2 )));
// Output:
// BMI: 24.21
// BMI: 23.94
// BMI: 23.67
Common Use Cases
import { combineLatest , map } from 'rxjs' ;
import { fromEvent } from 'rxjs' ;
const emailInput = document . querySelector ( '#email' );
const passwordInput = document . querySelector ( '#password' );
const email$ = fromEvent ( emailInput , 'input' ). pipe (
map (( e : any ) => e . target . value )
);
const password$ = fromEvent ( passwordInput , 'input' ). pipe (
map (( e : any ) => e . target . value )
);
const formValid$ = combineLatest ([ email$ , password$ ]). pipe (
map (([ email , password ]) => {
return email . includes ( '@' ) && password . length >= 8 ;
})
);
formValid$ . subscribe ( valid => {
const submitButton = document . querySelector ( '#submit' ) as HTMLButtonElement ;
submitButton . disabled = ! valid ;
});
Multiple API Calls
Parallel Requests
With Loading State
import { combineLatest } from 'rxjs' ;
import { ajax } from 'rxjs/ajax' ;
import { map } from 'rxjs/operators' ;
const user$ = ajax . getJSON ( '/api/user/1' );
const posts$ = ajax . getJSON ( '/api/user/1/posts' );
const comments$ = ajax . getJSON ( '/api/user/1/comments' );
combineLatest ([ user$ , posts$ , comments$ ]). pipe (
map (([ user , posts , comments ]) => ({
... user ,
postsCount: posts . length ,
commentsCount: comments . length
}))
). subscribe ( profile => {
console . log ( 'User profile:' , profile );
});
Real-time Dashboard
import { combineLatest , interval } from 'rxjs' ;
import { switchMap , map } from 'rxjs/operators' ;
import { ajax } from 'rxjs/ajax' ;
// Poll different endpoints at different intervals
const metrics$ = interval ( 5000 ). pipe (
switchMap (() => ajax . getJSON ( '/api/metrics' ))
);
const alerts$ = interval ( 10000 ). pipe (
switchMap (() => ajax . getJSON ( '/api/alerts' ))
);
const status$ = interval ( 2000 ). pipe (
switchMap (() => ajax . getJSON ( '/api/status' ))
);
const dashboard$ = combineLatest ({
metrics: metrics$ ,
alerts: alerts$ ,
status: status$
});
dashboard$ . subscribe ( data => {
updateDashboard ( data );
});
Filter with Multiple Criteria
import { combineLatest , BehaviorSubject } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
interface Product {
name : string ;
category : string ;
price : number ;
}
const products : Product [] = [
{ name: 'Laptop' , category: 'Electronics' , price: 1000 },
{ name: 'Mouse' , category: 'Electronics' , price: 20 },
{ name: 'Desk' , category: 'Furniture' , price: 300 }
];
const categoryFilter$ = new BehaviorSubject < string >( 'all' );
const priceFilter$ = new BehaviorSubject < number >( Infinity );
const searchTerm$ = new BehaviorSubject < string >( '' );
const filteredProducts$ = combineLatest ([
categoryFilter$ ,
priceFilter$ ,
searchTerm$
]). pipe (
map (([ category , maxPrice , search ]) => {
return products . filter ( p => {
const matchesCategory = category === 'all' || p . category === category ;
const matchesPrice = p . price <= maxPrice ;
const matchesSearch = p . name . toLowerCase (). includes ( search . toLowerCase ());
return matchesCategory && matchesPrice && matchesSearch ;
});
})
);
filteredProducts$ . subscribe ( products => {
console . log ( 'Filtered products:' , products );
});
// Update filters
categoryFilter$ . next ( 'Electronics' );
priceFilter$ . next ( 500 );
searchTerm$ . next ( 'lap' );
Behavior Details
Waiting for First Emissions
combineLatest will NOT emit until ALL Observables have emitted at least once:
import { combineLatest , timer , NEVER } from 'rxjs' ;
const fast$ = timer ( 0 , 100 ); // Emits immediately and every 100ms
const never$ = NEVER ; // Never emits
combineLatest ([ fast$ , never$ ]). subscribe (
x => console . log ( x ) // This will NEVER run
);
Completion Behavior
import { combineLatest , of , EMPTY , NEVER } from 'rxjs' ;
import { delay } from 'rxjs/operators' ;
// Completes when ALL complete
combineLatest ([
of ( 1 ). pipe ( delay ( 100 )),
of ( 2 ). pipe ( delay ( 200 ))
]). subscribe ({
next : x => console . log ( x ),
complete : () => console . log ( 'Complete!' ) // After 200ms
});
// Never completes if one never completes
combineLatest ([
of ( 1 ),
NEVER
]). subscribe ({
next : x => console . log ( x ), // [1, ???] - waits forever
complete : () => console . log ( 'Complete!' ) // Never called
});
// Completes immediately if one completes without emitting
combineLatest ([
of ( 1 ),
EMPTY
]). subscribe ({
next : x => console . log ( x ), // Never called
complete : () => console . log ( 'Complete!' ) // Called immediately
});
combineLatest emits every time ANY input Observable emits. With many frequently-emitting Observables, this can create performance issues. Consider using debounceTime or throttleTime if needed.
import { combineLatest , fromEvent } from 'rxjs' ;
import { debounceTime , map } from 'rxjs/operators' ;
const mouseMove$ = fromEvent ( document , 'mousemove' );
const scroll$ = fromEvent ( document , 'scroll' );
// This emits VERY frequently
combineLatest ([ mouseMove$ , scroll$ ]). subscribe ( ... );
// Better: debounce the result
combineLatest ([ mouseMove$ , scroll$ ]). pipe (
debounceTime ( 100 )
). subscribe ( ... );
Comparison with Other Operators
combineLatest vs zip: combineLatest uses the latest values, zip waits for corresponding indexed values from each Observable.combineLatest vs withLatestFrom: combineLatest emits when ANY source emits, withLatestFrom only emits when the primary Observable emits.combineLatest vs forkJoin: combineLatest emits continuously, forkJoin emits only once when all complete.
zip - Combine by index position
forkJoin - Wait for all to complete
merge - Emit from all concurrently
withLatestFrom - Sample latest values from other Observables
See Also