combineLatestWith - Combine Latest Values Within a Pipeline
The combineLatestWith operator combines the latest values of the original Observable and other specified Observables into a new stream. This is the Pipeable Operator version of the Creation Function combineLatest.
🔰 Basic Syntax and Usage
import { interval } from 'rxjs';
import { combineLatestWith, map } from 'rxjs';
const source1$ = interval(1000); // 0, 1, 2, ...
const source2$ = interval(1500); // 0, 1, 2, ...
source1$
.pipe(
combineLatestWith(source2$),
map(([val1, val2]) => `Stream1: ${val1}, Stream2: ${val2}`)
)
.subscribe(console.log);
// Example output:
// Stream1: 0, Stream2: 0
// Stream1: 1, Stream2: 0
// Stream1: 2, Stream2: 0
// Stream1: 2, Stream2: 1
// Stream1: 3, Stream2: 1
// ...- Wait until all streams have issued at least once, then output the latest value combination every time one of them issues.
- Because it is accepted in tuple form, it is type-safe in TypeScript.
🌐 RxJS Official Documentation - combineLatestWith
💡 Typical Usage Patterns
- Real-time form validation: Combine and validate the latest values of multiple fields
- Multiple sensor integration: Simultaneous display of different frequencies of data such as temperature, humidity, etc.
- Combined search filters: Integrate category selection and keyword entry
- Live preview: Real-time preview combining multiple configuration values
🧠 Practical Code Example (with UI)
Example of real-time color (RGB) change with multiple sliders.
import { fromEvent, combineLatest } from 'rxjs';
import { map, startWith, combineLatestWith } from 'rxjs';
// Build the UI
const container = document.createElement('div');
container.innerHTML = `
<h3>combineLatestWith Practical Example: RGB Color Picker</h3>
<div>
<label>Red: <input type="range" id="red" min="0" max="255" value="128"></label>
<span id="red-value">128</span>
</div>
<div>
<label>Green: <input type="range" id="green" min="0" max="255" value="128"></label>
<span id="green-value">128</span>
</div>
<div>
<label>Blue: <input type="range" id="blue" min="0" max="255" value="128"></label>
<span id="blue-value">128</span>
</div>
<div id="preview" style="width: 200px; height: 100px; border: 1px solid #ccc; margin-top: 10px;"></div>
`;
document.body.appendChild(container);
// Get slider elements
const redSlider = document.getElementById('red') as HTMLInputElement;
const greenSlider = document.getElementById('green') as HTMLInputElement;
const blueSlider = document.getElementById('blue') as HTMLInputElement;
const redValue = document.getElementById('red-value')!;
const greenValue = document.getElementById('green-value')!;
const blueValue = document.getElementById('blue-value')!;
const preview = document.getElementById('preview')!;
// Stream for each slider
const red$ = fromEvent(redSlider, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value)),
startWith(128)
);
const green$ = fromEvent(greenSlider, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value)),
startWith(128)
);
const blue$ = fromEvent(blueSlider, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value)),
startWith(128)
);
// ✅ Pipeable Operator version - integrate others into main stream
red$
.pipe(
combineLatestWith(green$, blue$)
)
.subscribe(([r, g, b]) => {
// Update value display
redValue.textContent = String(r);
greenValue.textContent = String(g);
blueValue.textContent = String(b);
// Update preview background color
preview.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
});- Moving any slider will immediately update the preview with the latest RGB values combined.
- After all sliders have been manipulated at least once, the latest combination is always reflected.
🔄 Difference from Creation Function combineLatest
Basic Differences
combineLatest (Creation Function) | combineLatestWith (Pipeable Operator) | |
|---|---|---|
| Usage Location | Used as independent function | Used within .pipe() chain |
| Syntax | combineLatest([obs1$, obs2$, obs3$]) | obs1$.pipe(combineLatestWith(obs2$, obs3$)) |
| First Stream | Treats all equally | Treats as main stream |
| Advantage | Simple and readable | Easy to combine with other operators |
Specific Usage Examples
Creation Function is Recommended for Simple Combination Only
import { combineLatest, fromEvent } from 'rxjs';
import { map } from 'rxjs';
const width$ = fromEvent(window, 'resize').pipe(map(() => window.innerWidth));
const height$ = fromEvent(window, 'resize').pipe(map(() => window.innerHeight));
// Simple and readable
combineLatest([width$, height$]).subscribe(([w, h]) => {
console.log(`Window size: ${w} x ${h}`);
});Pipeable Operator is Recommended When Adding Transformation Processing to Main Stream
import { fromEvent, interval } from 'rxjs';
import { combineLatestWith, map, startWith, throttleTime } from 'rxjs';
const clicks$ = fromEvent(document, 'click');
const timer$ = interval(1000);
// ✅ Pipeable Operator version - completed in one pipeline
clicks$
.pipe(
throttleTime(500), // Prevent rapid clicking
map(() => Date.now()), // Convert to timestamp
startWith(0), // Set initial value
combineLatestWith(timer$), // Integrate with timer
map(([clickTime, tick]) => ({
lastClick: clickTime,
elapsed: tick
}))
)
.subscribe(data => {
console.log(`Last click: ${data.lastClick}, Elapsed: ${data.elapsed} seconds`);
});
// ❌ Creation Function version - becomes verbose
import { combineLatest } from 'rxjs';
combineLatest([
clicks$.pipe(
throttleTime(500),
map(() => Date.now()),
startWith(0)
),
timer$
]).pipe(
map(([clickTime, tick]) => ({
lastClick: clickTime,
elapsed: tick
}))
).subscribe(data => {
console.log(`Last click: ${data.lastClick}, Elapsed: ${data.elapsed} seconds`);
});Summary
combineLatest: Optimal for simply combining multiple streamscombineLatestWith: Optimal when you want to integrate other streams while transforming or processing the main stream
⚠️ Important Notes
Wait Until All Streams Emit at Least Once
Values will not be output until all Observables have emitted at least once.
import { of, timer } from 'rxjs';
import { combineLatestWith } from 'rxjs';
of(1, 2, 3).pipe(
combineLatestWith(
timer(1000), // Emits after 1 second
)
).subscribe(console.log);
// Output: [3, 0]
// * Waits until timer$ emits, then combines with the latest value (3) of of() at that timeBeware of High Frequency Updates
If either stream is updated frequently, the combined result will be output frequently accordingly.
import { interval } from 'rxjs';
import { combineLatestWith, take } from 'rxjs';
interval(100).pipe(
take(5),
combineLatestWith(interval(1000).pipe(take(3)))
).subscribe(console.log);
// Output:
// [0, 0]
// [1, 0]
// [2, 0]
// [3, 0]
// [4, 0]
// [4, 1]
// [4, 2]Control update frequency with throttleTime or debounceTime as needed.
import { fromEvent, interval } from 'rxjs';
import { combineLatestWith, throttleTime, map } from 'rxjs';
const mouseMoves$ = fromEvent(document, 'mousemove').pipe(
throttleTime(100), // Limit every 100ms
map(e => ({ x: (e as MouseEvent).clientX, y: (e as MouseEvent).clientY }))
);
const timer$ = interval(1000);
mouseMoves$
.pipe(combineLatestWith(timer$))
.subscribe(([pos, tick]) => {
console.log(`Position: (${pos.x}, ${pos.y}), Tick: ${tick}`);
});Error Handling
If an error occurs in any Observable, the entire stream terminates with an error.
import { throwError, interval } from 'rxjs';
import { combineLatestWith, take, catchError } from 'rxjs';
import { of } from 'rxjs';
interval(1000).pipe(
take(2),
combineLatestWith(
throwError(() => new Error('Error occurred')).pipe(
catchError(err => of('Error recovered'))
)
)
).subscribe({
next: console.log,
error: err => console.error('Error:', err.message)
});
// Output: [1, 'Error recovered']📚 Related Operators
- combineLatest - Creation Function version
- zipWith - Pair corresponding values (order guaranteed)
- withLatestFrom - Combine only when main stream emits