Observable Primitives — API Reference
@tagna/udiot v1.0.0-rc.1
import {
Observable,
Subject,
BehaviorSubject,
ReplaySubject,
AsyncSubject
} from '@tagna/udiot';
Core Interfaces
interface Observer<T> {
next: (value: T) => void;
error?: (error: any) => void;
complete?: () => void;
}
interface Subscription {
unsubscribe: () => void;
closed: boolean;
}
Observable<T>
A cold observable. The producer function runs once per subscriber, so each subscriber gets its own independent execution.
class Observable<T> {
constructor(producer: (observer: Observer<T>) => (() => void) | void)
}
subscribe(observerOrNext, onError?, onComplete?)
subscribe(
observerOrNext: Observer<T> | ((value: T) => void),
onError?: (error: any) => void,
onComplete?: () => void
): Subscription
Executes the producer. Returns a Subscription with an unsubscribe() method and a closed boolean. Calling unsubscribe() stops delivery and runs any teardown returned by the producer.
const obs$ = new Observable<number>(observer => {
let i = 0;
const id = setInterval(() => observer.next(i++), 1000);
return () => clearInterval(id); // teardown
});
const sub = obs$.subscribe(n => console.log(n));
setTimeout(() => sub.unsubscribe(), 5000); // stops after ~5 values
Instance Operators
All operators return a new Observable and do not modify the source.
| Method | Signature | Description |
|---|---|---|
map |
(fn: T → U) → Observable<U> |
Transform each value |
filter |
(fn: T → boolean) → Observable<T> |
Pass only matching values |
debounce |
(delay: number) → Observable<T> |
Emit after silence of delay ms |
throttle |
(delay: number) → Observable<T> |
Emit at most once per delay ms |
take |
(count: number) → Observable<T> |
Complete after count values |
skip |
(count: number) → Observable<T> |
Ignore first count values |
takeUntil |
(notifier: Observable<any>) → Observable<T> |
Complete when notifier emits |
combineLatest |
(other: Observable<U>) → Observable<[T,U]> |
Emit when either emits, with latest of both |
merge |
(other: Observable<T>) → Observable<T> |
Emit from both sources |
switchMap |
(fn: T → Observable<U>) → Observable<U> |
Switch to inner observable on each emission |
catchError |
(fn: err → Observable<T>) → Observable<T> |
Replace errors with a fallback stream |
tap |
(fn: T → void) → Observable<T> |
Side-effect without affecting the stream |
toPromise |
() → Promise<T | undefined> |
Resolve with the first emitted value |
Static Creation Methods
Observable.of<T>(...values: T[]): Observable<T>
Observable.from<T>(array: T[] | ArrayLike<T>): Observable<T>
Observable.fromPromise<T>(promise: Promise<T>): Observable<T>
Observable.interval(ms: number): Observable<number>
Observable.timer(delay: number): Observable<number>
Observable.never<T>(): Observable<T>
Observable.empty<T>(): Observable<T>
Observable.throw<T>(error: any): Observable<T>
Subject<T>
A hot observable. Multicasts to all current subscribers. Extends Observable<T> so it can be passed anywhere an Observable is expected.
class Subject<T> extends Observable<T> {
next(value: T): void
error(err: any): void
complete(): void
subscribe(observerOrNext: Observer<T> | ((value: T) => void), ...): Subscription
asObservable(): Observable<T>
get observerCount(): number
get closed(): boolean
}
A Subject is stopped (closed = true) after error() or complete() is called. Further calls to next() are silently ignored.
const events$ = new Subject<string>();
events$.subscribe(e => console.log('handler 1:', e));
events$.subscribe(e => console.log('handler 2:', e));
events$.next('click');
// handler 1: click
// handler 2: click
events$.complete();
events$.next('after complete'); // ignored
BehaviorSubject<T>
Extends Subject<T>. Replays the most recent value to each new subscriber synchronously at the moment of subscription.
class BehaviorSubject<T> extends Subject<T> {
constructor(initialValue: T)
get value(): T // Throws if closed or errored
getValue(): T | undefined // Safe — returns undefined if closed or errored
next(value: T): void // Updates current value then multicasts
subscribe(...): Subscription // Emits current value immediately to new subscriber
}
value (the getter) throws if the subject is closed or has errored. Use getValue() for a non-throwing alternative.
const count$ = new BehaviorSubject<number>(0);
count$.subscribe(n => console.log('A:', n)); // A: 0 (immediate)
count$.next(1); // A: 1
count$.subscribe(n => console.log('B:', n)); // B: 1 (immediate — gets latest)
count$.next(2); // A: 2, B: 2
ReplaySubject<T>
Extends Subject<T>. Stores up to bufferSize past values and replays them to each new subscriber.
class ReplaySubject<T> extends Subject<T> {
constructor(bufferSize?: number) // Default: Infinity
next(value: T): void
subscribe(...): Subscription // Replays buffered values first
getBuffer(): T[]
clearBuffer(): void
}
const log$ = new ReplaySubject<string>(3); // keep last 3
log$.next('a');
log$.next('b');
log$.next('c');
log$.next('d'); // 'a' is evicted
// Late subscriber gets 'b', 'c', 'd' immediately
log$.subscribe(s => console.log(s));
AsyncSubject<T>
Extends Subject<T>. Buffers all values but emits only the last one — and only when complete() is called.
class AsyncSubject<T> extends Subject<T> {
next(value: T): void // Stores but does not emit
complete(): void // Emits last stored value then completes
}
const result$ = new AsyncSubject<number>();
result$.subscribe(n => console.log('result:', n));
result$.next(1);
result$.next(2);
result$.next(3);
result$.complete(); // emits 3, then completes
// result: 3
Useful for wrapping a single async operation that you want to multicast.
Examples
BehaviorSubject Powering a Count Display
const count$ = new BehaviorSubject(0);
function renderCount(n: number): void {
document.getElementById('counter')!.textContent = String(n);
}
count$.subscribe(renderCount); // renders 0 immediately
document.getElementById('inc')!.addEventListener('click', () => {
count$.next(count$.value + 1);
});
Subject as Event Bus
const eventBus$ = new Subject<{ type: string; payload?: any }>();
// Producer
function emitEvent(type: string, payload?: any): void {
eventBus$.next({ type, payload });
}
// Consumers
eventBus$
.filter(e => e.type === 'USER_LOGIN')
.subscribe(e => console.log('User logged in:', e.payload));
eventBus$
.filter(e => e.type === 'PURCHASE')
.subscribe(e => analytics.track('purchase', e.payload));
emitEvent('USER_LOGIN', { userId: 42 });
emitEvent('PURCHASE', { amount: 99.99 });
Observable from a Timer
const ticker$ = Observable.interval(1000); // emits 0, 1, 2, … every second
const sub = ticker$
.take(5)
.map(n => `tick ${n}`)
.subscribe(msg => console.log(msg));
// Automatically unsubscribes after 5 values because take(5) calls complete()
Unsubscribe Cleanup Pattern in Class Lifecycle
class DataComponent {
private subscriptions: (() => void)[] = [];
mount(store: Store<AppState>): void {
const sub1 = store
.select(s => s.users)
.subscribe(users => this.renderUsers(users));
const sub2 = store
.select(s => s.loading)
.subscribe(loading => this.setSpinner(loading));
this.subscriptions.push(
() => sub1.unsubscribe(),
() => sub2.unsubscribe()
);
}
unmount(): void {
this.subscriptions.forEach(unsub => unsub());
this.subscriptions = [];
}
private renderUsers(users: User[]): void { /* ... */ }
private setSpinner(loading: boolean): void { /* ... */ }
}