Building custom Observables allows you to wrap any asynchronous event—like a timer, a web socket, or a custom user interaction—into a standardized reactive interface.
1The Producer Logic
The function you pass to the new Observable constructor is the 'producer'. It defines what happens when someone starts listening. Within this function, you have access to the observer object. By calling observer.next(value), you send data to all subscribers. This pattern is incredibly flexible; you can emit values synchronously, on a timer, or in response to complex external events. It's the engine that powers the stream.
2Terminating the Stream
An Observable lifecycle typically ends in one of two ways: success or failure. Calling observer.complete() sends a completion notification, after which the Observable will never emit another value. Calling observer.error(err) sends an error notification and also terminates the stream. Understanding these termination points is critical for writing reliable code that knows when to stop processing and when to clean up resources.
3Step-by-Step Breakdown
While Angular gives us many Observables (like in HTTP), sometimes we need to build our own. Let's learn how to create a stream from scratch.
We use the 'new Observable' constructor. It takes a function that receives an 'observer'. This observer is your remote control for the stream.
Inside, we use 'observer.next()' to push data. You can push as many values as you want, whenever you want.
Checkpoint: Which method on the observer object is used to send a new piece of data to subscribers?
- →send()
- →next()
To end the stream, we call 'observer.complete()'. This tells everyone that no more data is coming. If an error occurs, we use 'observer.error()'.
Crucially, subscriptions are like open faucets. If you don't turn them off, they leak memory. Always store the subscription and call 'unsubscribe()'.
Checkpoint: What should you do when a component is destroyed to prevent memory leaks from active Observables?
- →Delete the variable
- →Call .unsubscribe() on the subscription object
Creation, emission, and cleanup. You now have full control over the lifecycle of your data streams!
Next, we'll see the real magic of RxJS: Operators.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1A Custom Observable Wrapping a DOM Event Should Preserve Keyboard Equivalents
If you wrap a `mousemove` or `mouseenter` DOM event into a custom Observable for a UI feature, make sure a parallel keyboard-accessible path (focus/blur, keydown) exists too — an Observable abstraction doesn't automatically fix an inaccessible underlying event source.
2Long-Lived Observable Streams Updating the UI Need Live Region Announcements
A custom Observable pushing periodic updates (like a real-time notification stream) should pipe its emissions into an `aria-live` region update, not just a silent DOM change, so screen reader users are aware of new information arriving.
SEO Implications
- 1
Observable-Driven Content Has No SEO Weight Until It's Actually Rendered as HTML
An Observable stream itself is just a JavaScript abstraction — crawlers only ever see the DOM state that results from its emissions being rendered (typically via the async pipe), and only if that rendering happens during a server-side render pass.
- 2
Poorly Managed Observable Subscriptions Can Degrade Long-Session Performance
A memory leak from unclosed custom Observable subscriptions accumulates over a long-lived SPA session, which can degrade responsiveness over time — an indirect but real concern for engagement-based metrics on session-heavy apps.
Best Practices
Always Return a Teardown Function From a Custom Observable's Subscriber Function
The function passed to `new Observable(subscriber => {...})` should return a cleanup callback (removing event listeners, clearing timers) — without it, unsubscribing from the Observable doesn't actually release the underlying resource it wrapped.
Prefer Existing RxJS Creation Functions Over Hand-Rolling an Observable When Possible
`fromEvent`, `interval`, and `timer` already correctly handle teardown and common edge cases for their respective use cases — reach for a fully custom `new Observable()` only when no existing creation function fits.
Frequent Bugs
Unsubscribing from a custom Observable doesn't actually stop the underlying behavior (a timer keeps firing, an event listener stays attached).
The subscriber function passed to `new Observable(subscriber => {...})` never returned a teardown/cleanup function. RxJS calls that returned function specifically when the subscription is unsubscribed — without it, whatever resource the Observable wrapped (a `setInterval`, an event listener) keeps running indefinitely.
A custom Observable wrapping a DOM event fires the initial subscriber logic multiple times for a single subscription.
Check whether the subscriber function has a side effect that runs on every subscription rather than being properly scoped — if the Observable is subscribed to multiple times (e.g., via multiple async pipe usages), each subscription independently re-runs the setup logic unless the stream is explicitly shared via an operator like `share()`.
Real-World Examples
Custom Observable Wrapping a WebSocket With Proper Teardown
A custom Observable wraps a WebSocket connection, correctly closing the connection when unsubscribed so navigating away from the component doesn't leave an orphaned open socket.
function fromSocket(url: string) {
return new Observable(subscriber => {
const ws = new WebSocket(url);
ws.onmessage = e => subscriber.next(e.data);
return () => ws.close();
});
}