For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Log inSign up
Support
GuidesReferenceClick-to-Call
GuidesReferenceClick-to-Call
  • Getting Started
    • Overview
    • Authentication
    • RxJS Primer
    • Migrate from v3
  • Web Components
    • Overview
    • Click-to-Call
    • Theming
    • Customization
  • Build Voice & Video Apps
    • Overview
    • Outbound Calls
    • Inbound Calls
    • Device Management
    • Screen Sharing
    • Call Controls
    • Layouts
    • Messaging & Chat
  • Manage Resources
    • Overview
    • Users
    • Address Book
    • Client Preferences
    • Capabilities
  • Deploy
    • Overview
    • Framework Integration
    • SSR & Next.js
    • Troubleshooting
LogoLogoSignalWire Docs
Log inSign up
Support
On this page
  • Observables and subscriptions
  • The $ suffix
  • Subscribing
  • Common patterns
  • Cleanup
  • References
Getting Started

RxJS Primer

|View as Markdown|Open in Claude|
Was this page helpful?
Edit this page
Previous

Migrate from v3

Next
Built with

Most state in the Browser SDK — devices, participants, capabilities, call status — is exposed as RxJS observables. This page covers the subset of RxJS used by the SDK. For everything else, see the RxJS docs.

Observables and subscriptions

An observable is a stream of values over time. It does nothing until .subscribe() is called; .subscribe() returns a subscription, and .unsubscribe() ends it. Subscriptions left open are the main source of memory leaks.

SDK observables behave like BehaviorSubjects: subscribing emits the current value synchronously, then every subsequent change.

Transform a stream with .pipe() and operators (filter, map, take, combineLatest, switchMap, debounceTime, etc.).

The $ suffix

A property ending in $ is the observable. The same name without $ is the current snapshot.

1const status = call.status; // current value
2call.status$.subscribe(handler); // current value + every change

Snapshots are not always populated. For state the SDK already holds in memory (call.status, call.participants, device lists), the snapshot is the current value and reading it is fine. For lazily-loaded collections — directory.addresses, address.textMessages, address.history — the snapshot starts empty until something subscribes to the corresponding $ observable (and, for paginated collections, until loadMore() is called). Reading the snapshot synchronously right after connecting will give you []. Subscribe to the $ form, then call loadMore() to trigger the first page.

Subscribing

1const sub = client.audioInputDevices$.subscribe((devices) => {
2 console.log("mics:", devices);
3});
4
5sub.unsubscribe();

The first emission fires synchronously with the current device list; subsequent emissions fire on device changes.

Common patterns

Wait for a value, then proceed once. take(1) ends the subscription after the first match.

1import { filter, take } from "rxjs";
2
3client.ready$
4 .pipe(filter(Boolean), take(1))
5 .subscribe(async () => {
6 const call = await client.dial(destination);
7 });

React on every match.

1import { filter } from "rxjs";
2
3call.status$.pipe(filter((s) => s === "connected")).subscribe(showCallControls);

Combine the latest of multiple streams.

1import { combineLatest } from "rxjs";
2
3combineLatest([client.audioInputDevices$, client.selectedAudioInputDevice$])
4 .subscribe(([devices, selected]) => {
5 const activeMic = devices.find((d) => d.deviceId === selected?.deviceId);
6 });

Skip the initial value when only changes matter.

1import { skip } from "rxjs";
2
3call.status$.pipe(skip(1)).subscribe(showStatusNotification);

Throttle high-frequency streams.

1import { debounceTime } from "rxjs";
2
3call.localAudioLevel$.pipe(debounceTime(100)).subscribe(updateVolumeIndicator);

Cleanup

Two patterns:

1class CallManager {
2 subs = [];
3 start(call) {
4 this.subs.push(
5 call.status$.subscribe(this.onStatus),
6 call.participants$.subscribe(this.onParticipants),
7 );
8 }
9 stop() {
10 this.subs.forEach((s) => s.unsubscribe());
11 this.subs = [];
12 }
13}
1import { Subject, takeUntil } from "rxjs";
2
3const destroy$ = new Subject();
4
5call.status$.pipe(takeUntil(destroy$)).subscribe(updateStatus);
6call.participants$.pipe(takeUntil(destroy$)).subscribe(updateParticipants);
7
8destroy$.next();
9destroy$.complete();

takeUntil scales better with many subscriptions; an array is fine for a few.

References

  • RxJS docs
  • Observables
  • Operators
  • Subjects (including BehaviorSubject)