combineLatest#
The combineLatest()
operator is used when you have multiple streams of data (Observables) and you want to combine their latest values into a single stream.
Think of it like this: You're waiting for several pieces of information to arrive. combineLatest()
waits until it has received at least one piece of information from every source it's watching. Once it has that initial set, it gives you an array containing the latest value from each source.
After that initial combination, any time any one of the sources sends out a new piece of information, combineLatest()
immediately sends out a new array containing that new value along with the most recent values it remembers from all the other sources.
Key Characteristics#
- Waits for Initial Values: It won't emit anything until all input Observables have emitted at least one value.
- Emits Latest Combination: Once initialized, it emits an array containing the most recent value from each input Observable.
- Reacts to Any Input: As soon as any of the input Observables emits a new value,
combineLatest
emits a new array with the updated combination.
Real-World Analogy#
Imagine a dashboard displaying:
- The current stock price for "Company A".
- The current user's selected currency (e.g., USD, EUR, INR).
You need both the latest price and the selected currency to display the price correctly formatted.
combineLatest()
would wait until it receives the first stock price update AND the first currency selection.- Once it has both, it emits
[latestPrice, latestCurrency]
. - If the stock price updates, it immediately emits
[newPrice, latestCurrency]
. - If the user changes the currency, it immediately emits
[latestPrice, newCurrency]
.
Angular Example: Combining Route Parameter and User Settings#
Let's say you have a component that displays product details. The product ID comes from the route URL, and you also have a user setting for whether to show prices in bold.
import { Component, OnInit, inject } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { combineLatest, Observable, timer } from "rxjs";
import {
map,
switchMap,
distinctUntilChanged,
filter,
delay,
} from "rxjs/operators";
import { Injectable } from "@angular/core";
import { of } from "rxjs";
@Injectable({ providedIn: "root" })
export class ProductService {
getProduct(id: string): Observable<any> {
console.log(`API: Fetching product ${id}`);
return of({
id: id,
name: `Product ${id}`,
price: Math.random() * 100,
}).pipe(delay(300));
}
}
@Injectable({ providedIn: "root" })
export class UserSettingsService {
getSettings(): Observable<{ boldPrice: boolean }> {
return timer(0, 5000).pipe(map((i) => ({ boldPrice: i % 2 === 0 })));
}
}
@Component({
selector: "app-product-detail",
template: `
<div *ngIf="data$ | async as data">
<h2>{{ data.product.name }}</h2>
<p [style.fontWeight]="data.settings.boldPrice ? 'bold' : 'normal'">
Price: {{ data.product.price | currency }}
</p>
</div>
<p *ngIf="!(data$ | async)">Loading product details...</p>
`,
})
export class ProductDetailComponent implements OnInit {
private route = inject(ActivatedRoute);
private productService = inject(ProductService);
private userSettingsService = inject(UserSettingsService);
data$!: Observable<{ product: any; settings: { boldPrice: boolean } }>;
ngOnInit() {
const productId$ = this.route.paramMap.pipe(
map((params) => params.get("productId")),
filter((id): id is string => !!id),
distinctUntilChanged()
);
const settings$ = this.userSettingsService.getSettings();
this.data$ = combineLatest([productId$, settings$]).pipe(
switchMap(([id, settings]) =>
this.productService
.getProduct(id)
.pipe(map((product) => ({ product, settings })))
)
);
}
}
In this example:
- We get the
productId
from the route parameters.distinctUntilChanged
prevents unnecessary work if the route emits the same ID multiple times. - We get the
settings
from a service. combineLatest
waits for both the first validproductId
and the firstsettings
emission.- When both are available, or when either the
productId
changes or thesettings
update,combineLatest
emits[latestId, latestSettings]
. switchMap
takes this latest combination. It uses thelatestId
to fetch the corresponding product data. If theproductId
changes while a previous product fetch is still in progress,switchMap
cancels the old fetch and starts a new one for the new ID.- Finally, we
map
the result to create an object{ product, settings }
that's easy to use in the template with theasync
pipe. The template automatically updates wheneverdata$
emits a new object, whether due to a product change or a setting change.
Summary#
use combineLatest()
when you need to react to changes from multiple independent sources and always need the most recent value from each of them to perform a calculation or update the UI.