combineLatestWith - combine the latest value
The combineLatestWith operator combines the latest values of the original Observable and any other Observable specified. Whenever a new value is issued from one of the Observables, a result is issued that combines all the latest values. This is the Pipeable Operator version of Creation Function's combineLatest.
🔰 Basic Syntax and Usage
import { interval } from 'rxjs';
import { combineLatestWith, map, take } from 'rxjs';
const source1$ = interval(1000).pipe(
map(val => `A${val}`),
take(3)
);
const source2$ = interval(1500).pipe(
map(val => `B${val}`),
take(2)
);
source1$
.pipe(combineLatestWith(source2$))
.subscribe(([val1, val2]) => {
console.log(`${val1} + ${val2}`);
});
// Output Examples:
// A0 + B0
// A1 + B0
// A2 + B0
// A2 + B1- After each Observable has issued at least one value, the combined value is output.
- Whenever a new value comes in for either one, the most recent pair is re-output.
🌐 RxJS Official Documentation - combineLatestWith
💡 Typical utilization pattern
- Real-time validation of form input: constantly monitor the latest state of multiple fields
- Synchronization of multiple dependent states: combination of configuration values and user input
- Real-time updating of calculation results: Instant calculation of results from multiple input values
🧠 Practical code example (with UI)
This example calculates the total amount in real time from price and quantity inputs.
import { fromEvent } from 'rxjs';
import { combineLatestWith, map, startWith } from 'rxjs';
// Output Area Creation
const output = document.createElement('div');
output.innerHTML = '<h3>combineLatestWith Practical Examples of:</h3>';
document.body.appendChild(output);
// Input Field Creation
const priceInput = document.createElement('input');
priceInput.type = 'number';
priceInput.placeholder = 'Unit Price';
priceInput.value = '100';
document.body.appendChild(priceInput);
const quantityInput = document.createElement('input');
quantityInput.type = 'number';
quantityInput.placeholder = 'Quantity';
quantityInput.value = '1';
document.body.appendChild(quantityInput);
// Result display area
const result = document.createElement('div');
result.style.fontSize = '20px';
result.style.marginTop = '10px';
document.body.appendChild(result);
// of each inputObservable
const price$ = fromEvent(priceInput, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value) || 0),
startWith(100)
);
const quantity$ = fromEvent(quantityInput, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value) || 0),
startWith(1)
);
// Calculate by combining the latest values
price$
.pipe(
combineLatestWith(quantity$),
map(([price, quantity]) => price * quantity)
)
.subscribe((total) => {
result.innerHTML = `<strong>Total amount: ¥${total.toLocaleString()}</strong>`;
});- When you enter either field, the total is immediately recalculated from the two most recent values.
- The
startWith()is used to get the combined result from the beginning.
🔄 Difference from Creation Function combineLatest.
Basic differences
import { interval } from 'rxjs';
import { combineLatestWith, map, take } from 'rxjs';
const source1$ = interval(1000).pipe(
map(val => `A${val}`),
take(3)
);
const source2$ = interval(1500).pipe(
map(val => `B${val}`),
take(2)
);
source1$
.pipe(combineLatestWith(source2$))
.subscribe(([val1, val2]) => {
console.log(`${val1} + ${val2}`);
});
// Output Examples:
// A0 + B0
// A1 + B0
// A2 + B0
// A2 + B1Specific examples of usage
If you only need simple combinations, Creation Function is the way to go.
import { combineLatest, of } from 'rxjs';
const firstName$ = of('Taro');
const lastName$ = of('Yamada');
const age$ = of(30);
// Simple and easy to read
combineLatest([firstName$, lastName$, age$]).subscribe(([first, last, age]) => {
console.log(`${last} ${first}3 (${age}Age)`);
});
// Output: Yamada Taro (30Age)If you want to add a conversion process to the main stream, Pipeable Operator is recommended.
import { fromEvent, interval } from 'rxjs';
import { combineLatestWith, map, startWith, debounceTime } from 'rxjs';
const searchInput = document.createElement('input');
searchInput.placeholder = 'Search...';
document.body.appendChild(searchInput);
const categorySelect = document.createElement('select');
categorySelect.innerHTML = '<option>All</option><option>Books</option><option>DVD</option>';
document.body.appendChild(categorySelect);
const output = document.createElement('div');
output.style.marginTop = '10px';
document.body.appendChild(output);
// Mainstream: Search keywords
const searchTerm$ = fromEvent(searchInput, 'input').pipe(
map(e => (e.target as HTMLInputElement).value),
debounceTime(300), // After input300msWait
startWith('')
);
// Sub Stream: Select category
const category$ = fromEvent(categorySelect, 'change').pipe(
map(e => (e.target as HTMLSelectElement).value),
startWith('All')
);
// ✅ Pipeable OperatorEdition - Complete in one pipeline
searchTerm$
.pipe(
map(term => term.toLowerCase()), // Convert to lower case
combineLatestWith(category$),
map(([term, category]) => ({
term,
category,
timestamp: new Date().toLocaleTimeString()
}))
)
.subscribe(result => {
output.textContent = `Search: "${result.term}" Category: ${result.category} [${result.timestamp}]`;
});
// ❌ Creation FunctionEdition - Become redundant
import { combineLatest } from 'rxjs';
combineLatest([
searchTerm$.pipe(map(term => term.toLowerCase())),
category$
]).pipe(
map(([term, category]) => ({
term,
category,
timestamp: new Date().toLocaleTimeString()
}))
).subscribe(result => {
output.textContent = `Search: "${result.term}" Category: ${result.category} [${result.timestamp}]`;
});If you want to combine multiple settings import { fromEvent } from 'rxjs'; import { combineLatestWith, map, startWith } from 'rxjs';
// Create slider const redSlider = document.createElement('input'); redSlider.type = 'range'; redSlider.min = '0'; redSlider.max = '255'; redSlider.value = '255'; document.body.appendChild(document.createTextNode('Red: ')); document.body.appendChild(redSlider); document.body.appendChild(document.createElement('br'));
const greenSlider = document.createElement('input'); greenSlider.type = 'range'; greenSlider.min = '0'; greenSlider.max = '255'; greenSlider.value = '0'; document.body.appendChild(document.createTextNode('Green: ')); document.body.appendChild(greenSlider); document.body.appendChild(document.createElement('br'));
const blueSlider = document.createElement('input'); blueSlider.type = 'range'; blueSlider.min = '0'; blueSlider.max = '255'; blueSlider.value = '0'; document.body.appendChild(document.createTextNode('Blue: ')); document.body.appendChild(blueSlider);
const colorBox = document.createElement('div'); colorBox.style.width = '200px'; colorBox.style.height = '100px'; colorBox.style.marginTop = '10px'; colorBox.style.border = '1px solid #ccc'; document.body.appendChild(colorBox);
// Mainstream: Red const red$ = fromEvent(redSlider, 'input').pipe( map(e => Number((e.target as HTMLInputElement).value)), startWith(255) );
// ✅ Pipeable OperatorEdition - Redcombine other colors as main red$ .pipe( combineLatestWith( fromEvent(greenSlider, 'input').pipe( map(e => Number((e.target as HTMLInputElement).value)), startWith(0) ), fromEvent(blueSlider, 'input').pipe( map(e => Number((e.target as HTMLInputElement).value)), startWith(0) ) ), map(([r, g, b]) => rgb(${r}, ${g}, ${b})) ) .subscribe(color => { colorBox.style.backgroundColor = color; colorBox.textContent = color; colorBox.style.display = 'flex'; colorBox.style.alignItems = 'center'; colorBox.style.justifyContent = 'center'; colorBox.style.color = '#fff'; colorBox.style.textShadow = '1px 1px 2px #000'; });
import { fromEvent } from 'rxjs';
import { combineLatestWith, map, startWith } from 'rxjs';
// Create slider
const redSlider = document.createElement('input');
redSlider.type = 'range';
redSlider.min = '0';
redSlider.max = '255';
redSlider.value = '255';
document.body.appendChild(document.createTextNode('Red: '));
document.body.appendChild(redSlider);
document.body.appendChild(document.createElement('br'));
const greenSlider = document.createElement('input');
greenSlider.type = 'range';
greenSlider.min = '0';
greenSlider.max = '255';
greenSlider.value = '0';
document.body.appendChild(document.createTextNode('Green: '));
document.body.appendChild(greenSlider);
document.body.appendChild(document.createElement('br'));
const blueSlider = document.createElement('input');
blueSlider.type = 'range';
blueSlider.min = '0';
blueSlider.max = '255';
blueSlider.value = '0';
document.body.appendChild(document.createTextNode('Blue: '));
document.body.appendChild(blueSlider);
const colorBox = document.createElement('div');
colorBox.style.width = '200px';
colorBox.style.height = '100px';
colorBox.style.marginTop = '10px';
colorBox.style.border = '1px solid #ccc';
document.body.appendChild(colorBox);
// Mainstream: Red
const red$ = fromEvent(redSlider, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value)),
startWith(255)
);
// ✅ Pipeable OperatorEdition - Redcombine other colors as main
red$
.pipe(
combineLatestWith(
fromEvent(greenSlider, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value)),
startWith(0)
),
fromEvent(blueSlider, 'input').pipe(
map(e => Number((e.target as HTMLInputElement).value)),
startWith(0)
)
),
map(([r, g, b]) => `rgb(${r}, ${g}, ${b})`)
)
.subscribe(color => {
colorBox.style.backgroundColor = color;
colorBox.textContent = color;
colorBox.style.display = 'flex';
colorBox.style.alignItems = 'center';
colorBox.style.justifyContent = 'center';
colorBox.style.color = '#fff';
colorBox.style.textShadow = '1px 1px 2px #000';
});Summary
combineLatest: best for simple combination of multiple streamscombineLatestWith: best if you want to combine the latest values from other streams while transforming or processing the main stream
⚠️ Notes
Not issued until initial values are available.
Results will not be output until all Observables have issued at least one value.
import { interval, NEVER } from 'rxjs';
import { combineLatestWith, take } from 'rxjs';
interval(1000).pipe(
take(3),
combineLatestWith(NEVER) // No value issuedObservable
).subscribe(console.log);
// No output (becauseNEVERNo output (because)This can be resolved by providing an initial value with startWith().
import { interval, NEVER } from 'rxjs';
import { combineLatestWith, take, startWith } from 'rxjs';
interval(1000).pipe(
take(3),
combineLatestWith(NEVER.pipe(startWith(null)))
).subscribe(console.log);
// Output: [0, null] → [1, null] → [2, null]Beware of frequent reissues
If any stream issues values frequently, the result will also be re-issued frequently.
import { interval } from 'rxjs';
import { combineLatestWith } from 'rxjs';
// 100msStreams issued for each
const fast$ = interval(100);
const slow$ = interval(1000);
fast$.pipe(
combineLatestWith(slow$)
).subscribe(console.log);
// slow$each time a value is issued,fast$is issued, it is combined with the latest value of
// → Performance must be watchedError Handling
If an error occurs in any Observable, the entire program 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('Errors occur')).pipe(
catchError((err: unknown) => of('Recovery'))
)
)
).subscribe({
next: console.log,
error: err => console.error(err.message)
});
// Output: [0, 'Recovery'] → [1, 'Recovery']📚 Related Operators
- combineLatest - Creation Function version
- withLatestFrom - triggered by mainstream only
- zipWith - pairs corresponding values