RxJS
Observable
Master RxJS Observables, covering the push vs pull model, subscription lifecycle, stream types, and memory leak prevention.
1. Learning Objectives
In this lesson, you will master the fundamental building block of reactive programming in Angular: the RxJS Observable. By the end of this topic, you will be able to:
- Differentiate between Pull-based (Functions, Promises) and Push-based (Observables) data communication models.
- Create custom Observables from scratch using the
new Observable()constructor. - Manage the subscription lifecycle, including subscribing to and unsubscribing from streams.
- Prevent memory leaks by properly releasing resources inside the Observable teardown block.
- Handle the three types of notifications: next, error, and complete.
2. Overview
An Observable is a representation of a stream of values or events that can be observed over time. Unlike Promises, which handle a single asynchronous event and execute immediately, Observables are lazy (they don't run until you subscribe) and can emit zero, one, or multiple values over time. This makes them perfect for handling user interactions, web socket connections, and HTTP requests in Angular.
3. Why This Topic Matters
Angular is built from the ground up to be reactive, and RxJS is its reactive engine.
- Unified Async Interface: Whether you are listening to a button click, fetching data from a database, or reading route parameter changes, Angular represents all of them as Observables.
- Memory Management: In SPA (Single Page Applications), components are mounted and destroyed frequently. Failing to unsubscribe from long-running Observables causes memory leaks, degraded performance, and unexpected background behavior.
4. Real-World Analogy
Think of an Observable like a **YouTube Channel Subscription**:
- The Creator (Observable/Producer): The YouTuber records and uploads videos. However, they don't force anyone to watch, and videos are uploaded over time (zero, one, or many).
- The Subscriber (Consumer): As a user, you subscribe to the channel. You only start receiving notifications for new videos *after* you subscribe (lazy execution).
- The Unsubscribe: If you lose interest, you unsubscribe. The channel stops sending you notifications, and you stop consuming their resources.
5. Core Concepts
Observables involve distinct actors and phases:
- Producer: The source of data (e.g., websocket, timer, keyboard events) wrapped inside the Observable.
- Observer: An object containing callback functions (`next`, `error`, `complete`) that consumes values emitted by the Observable.
- Subscription: An object representing the execution of an Observable, primarily used to cancel (unsubscribe) the execution.
- Teardown Logic: A custom function returned inside the Observable constructor that releases system resources (e.g., clears intervals, removes event listeners) upon unsubscription.
| Concept | Promises | Observables |
|---|---|---|
| Evaluation | Eager (runs immediately on creation) | Lazy (runs only when subscribed) |
| Emissions | Single value (resolved/rejected) | Multiple values over time (0 to infinite) |
| Cancellability | Non-cancellable natively | Cancellable via unsubscribe |
6. Syntax & API Reference
Below is the syntax to create a custom Observable and consume its notifications:
7. Visual Diagram
The diagram below tracks the lifecycle of an Observable from creation to subscription and teardown:
8. Live Example — Full Working Code
Here is a complete standalone Angular component that handles geolocation tracking reactively, managing active connections transparently:
What just happened? Inside ngOnInit, we create a lazy geolocation observable. Upon calling subscribe(), the browser starts tracking coordinates, pushing them to the component state via next(). When the component gets destroyed, ngOnDestroy() triggers the unsubscribe() call, executing the teardown function which clears the geolocation watcher in the browser background.
9. Interactive Playground
Try It Yourself Challenges:
- Modify the live geolocation example to emit a maximum of 5 updates, then automatically call
observer.complete()inside the generator block. - Experiment with removing the
clearWatchteardown. Keep moving locations, destroy the component, and check the browser console to verify active processes continue leaking.
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Leaving Subscriptions Active | Forgetting to unsubscribe on component destruction, keeping references in memory. | ngOnInit() { stream.subscribe(...) } |
ngOnDestroy() { this.sub.unsubscribe() } |
| Omit Teardown Cleanup Function | Setting intervals or event listeners inside the constructor without returning a cleanup function. | new Observable(obs => { setInterval(...) }) |
return () => clearInterval(id); |
11. Best Practices
- Always Unsubscribe: Avoid memory leaks by unsubscribing in
ngOnDestroy, using operators liketakeUntil(), or leveraging theasyncpipe. - Use Dollar Sign Suffix: Follow standard naming conventions and append a `$` to observable streams (e.g.,
users$) to identify them easily. - Always Return Teardown Logic: When constructing custom Observables, return a clean-up function to prevent lingering background intervals or event handlers.
- Handle Errors Gracefully: Observables immediately terminate if they encounter an unhandled error. Always implement the
errorcallback.
12. Browser Compatibility/Requirements
RxJS is a pure library built on ES6 features (like Classes and Promises). It runs in all modern browsers (Chrome, Firefox, Edge, Safari) and works seamlessly out of the box in Angular environments.
13. Interview Questions
Q1: Explain the difference between Promises and Observables.
Answer: Promises are eager (execute code immediately upon creation), single-value emitters, and cannot be natively cancelled. Observables are lazy (only execute when subscribed to), emit multiple values over time (0 to infinite), and can be cancelled at any point by calling unsubscribe().
Q2: What happens if you do not unsubscribe from a custom observable interval when a component is destroyed?
Answer: The background interval will continue to execute even after the component is destroyed. This retains a reference to the component instance in memory, causing a memory leak, and performs computations that waste CPU cycles and may cause side effects.
14. Debugging Exercise
Identify why the stream values are not displaying in the terminal when running this script:
Diagnosis: Observables are lazy. The code inside the generator block will not execute, nor will any logs print, because nothing has subscribed to myStream$.
Fix: Call .subscribe() on the observable to trigger its execution.
15. Practice Exercises
Exercise 1: Wrap window resize events inside an Observable
Create a custom Observable that listens to the global window.onresize event and emits the current width. Implement teardown logic to correctly remove the event listener from the window object when unsubscribed.
16. Scenario-Based Challenge
The Inactivity Timer Challenge:
Build a custom Observable representing a user inactivity timeout. Reset a 10-second timer on mouse clicks. If no click occurs within 10 seconds, emit a timeout message. Ensure you clean up mouse event listeners and timeouts when completed or cancelled.
17. Quick Quiz
Q1: Which event notifies the observer that the stream is closing and will emit no further values?
A) next
B) complete
C) error
Answer: B — The complete event signals normal termination of the stream.
18. Summary & Key Takeaways
- Observables are lazy streams of data that only execute upon subscription.
- Observables support 3 types of callbacks:
next(),error(), andcomplete(). - Properly clearing references or event listeners inside the returned cleanup block prevents memory leaks.
- Observables can emit multiple values over time, unlike Promises which emit exactly once.
19. Cheat Sheet
| API / Concept | Purpose |
|---|---|
new Observable(sub => {}) |
Creates a custom stream from scratch, executing data-generation logic. |
subscriber.next(val) |
Emits the next value in the stream sequence to observers. |
subscriber.complete() |
Terminates the stream successfully and invokes teardown logic. |
subscription.unsubscribe() |
Cancels stream subscription and frees associated resources. |