Skip to main content

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

  1. Subscribes to both the source and notifier Observables
  2. Caches the most recent value from the source (if any)
  3. 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
  4. Clears the cache after emitting
  5. 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);
});

Sample Form Values on Submit

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);
});
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

OperatorTriggerTiming
sampleAnother Observable emitsEvent-driven
sampleTimeFixed intervalTime-based
auditDuration Observable completesAfter waiting period
throttleFirst value + durationRate-limiting