Overview
Emits the most recently emitted value from the source Observable whenever another Observable, the notifier, emits.
sample only emits when the notifier emits and the source has emitted at least one value since the last sample. If the source hasn’t emitted anything, nothing is sampled.
Type Signature
function sample < T >(
notifier : ObservableInput < any >
) : MonoTypeOperatorFunction < T >
Parameters
notifier
ObservableInput<any>
required
The ObservableInput to use for sampling the source Observable. Whenever this Observable emits a value (or completes), sample looks at the source and emits its most recent value (if any).
Returns
MonoTypeOperatorFunction<T> - A function that returns an Observable that emits the results of sampling the values emitted by the source Observable whenever the notifier Observable emits or completes.
How It Works
Subscribes to both the source and notifier Observables
Caches the most recent value from the source (if any)
When the notifier emits :
If source has emitted at least one value since last sample: emits the cached value
If source hasn’t emitted: does nothing
Clears the cache after emitting
Completes when the source completes
Think of sample as taking a “snapshot” of the source value whenever the notifier signals.
Usage Examples
Basic Example: Sample on Click
import { fromEvent , interval , sample } from 'rxjs' ;
const seconds = interval ( 1000 );
const clicks = fromEvent ( document , 'click' );
const result = seconds . pipe ( sample ( clicks ));
result . subscribe ( x => console . log ( x ));
// Every time you click, it logs the current second count
Sample Mouse Position on Key Press
import { fromEvent , sample , map } from 'rxjs' ;
const mouseMoves$ = fromEvent < MouseEvent >( document , 'mousemove' ). pipe (
map ( e => ({ x: e . clientX , y: e . clientY }))
);
const keyPress$ = fromEvent ( document , 'keypress' );
const sampledPosition$ = mouseMoves$ . pipe (
sample ( keyPress$ )
);
sampledPosition$ . subscribe ( pos => {
console . log ( 'Mouse position at keypress:' , pos );
});
import { fromEvent , sample , map } from 'rxjs' ;
const input = document . querySelector ( '#search' ) as HTMLInputElement ;
const button = document . querySelector ( '#submit' ) as HTMLButtonElement ;
const inputChanges$ = fromEvent ( input , 'input' ). pipe (
map ( e => ( e . target as HTMLInputElement ). value )
);
const buttonClicks$ = fromEvent ( button , 'click' );
const submittedValues$ = inputChanges$ . pipe (
sample ( buttonClicks$ )
);
submittedValues$ . subscribe ( value => {
console . log ( 'Submitted value:' , value );
performSearch ( value );
});
Periodic Sampling
User Activity Monitoring
Sample API Status
import { fromEvent , sample , interval } from 'rxjs' ;
const scrolls$ = fromEvent ( window , 'scroll' );
const sampler$ = interval ( 1000 ); // Sample every second
const sampledScrolls$ = scrolls$ . pipe (
sample ( sampler$ )
);
sampledScrolls$ . subscribe (() => {
const scrollY = window . scrollY ;
console . log ( 'Scroll position (sampled):' , scrollY );
// Update scroll indicator
});
When to Use
Use sample when:
You want to sample a fast stream at specific trigger points
Reducing event frequency based on another event
User-triggered sampling (click to capture current state)
Event-driven rate limiting
Don’t use sample when:
You need time-based sampling (use sampleTime instead)
You want every value from the source
You need the first value in a window (use throttle)
You need the last value after silence (use debounce)
Common Patterns
Capture State on Demand
import { BehaviorSubject , sample , fromEvent } from 'rxjs' ;
const state$ = new BehaviorSubject ({ count: 0 , status: 'idle' });
const captureButton$ = fromEvent (
document . querySelector ( '#capture' ) as HTMLButtonElement ,
'click'
);
const capturedState$ = state$ . pipe (
sample ( captureButton$ )
);
capturedState$ . subscribe ( state => {
console . log ( 'Captured state:' , state );
saveSnapshot ( state );
});
Sample on Multiple Events
import { fromEvent , sample , merge } from 'rxjs' ;
const temperature$ = getTemperatureStream ();
const button1$ = fromEvent ( document . querySelector ( '#sample1' ) ! , 'click' );
const button2$ = fromEvent ( document . querySelector ( '#sample2' ) ! , 'click' );
const timer$ = interval ( 30000 ); // Auto-sample every 30s
const samplers$ = merge ( button1$ , button2$ , timer$ );
const sampledTemp$ = temperature$ . pipe (
sample ( samplers$ )
);
sampledTemp$ . subscribe ( temp => {
console . log ( 'Temperature sample:' , temp );
});
Manual Refresh Trigger
import { sample , Subject , switchMap } from 'rxjs' ;
import { ajax } from 'rxjs/ajax' ;
const refresh$ = new Subject < void >();
const data$ = ajax . getJSON ( '/api/data' );
const refreshableData$ = data$ . pipe (
sample ( refresh$ )
);
refreshableData$ . subscribe ( data => {
console . log ( 'Refreshed data:' , data );
displayData ( data );
});
// Trigger refresh manually
function refreshData () {
refresh$ . next ();
}
If the notifier emits before the source has emitted anything, no value will be sampled (nothing happens).
Sample Latest Value
import { interval , sample , Subject } from 'rxjs' ;
const counter$ = interval ( 100 ); // Fast counter
const sampler$ = new Subject < void >();
const sampled$ = counter$ . pipe (
sample ( sampler$ )
);
sampled$ . subscribe ( count => {
console . log ( 'Current count:' , count );
});
// Sample at different times
setTimeout (() => sampler$ . next (), 500 ); // ~5
setTimeout (() => sampler$ . next (), 1200 ); // ~12
setTimeout (() => sampler$ . next (), 2000 ); // ~20
Behavior with Notifier Completion
When the notifier completes , it triggers one final sample:
import { interval , sample , take } from 'rxjs' ;
const source$ = interval ( 100 );
const notifier$ = interval ( 500 ). pipe ( take ( 3 )); // Emits 3 times then completes
const sampled$ = source$ . pipe (
sample ( notifier$ )
);
sampled$ . subscribe (
value => console . log ( 'Sampled:' , value ),
err => console . error ( 'Error:' , err ),
() => console . log ( 'Complete' )
);
// Output:
// Sampled: ~4 (at 500ms)
// Sampled: ~9 (at 1000ms)
// Sampled: ~14 (at 1500ms)
// Complete (source continues but notifier completed)
Combine sample with distinctUntilChanged to avoid emitting duplicate samples when the source value hasn’t changed.
Comparison with Similar Operators
Operator Trigger Timing sampleAnother Observable emitsEvent-driven sampleTimeFixed interval Time-based auditDuration Observable completesAfter waiting period throttleFirst value + durationRate-limiting