angular/aio/content/guide/comparing-observables.md
Ward Bell acd59ad037 docs: Migrate Observables guides & code examples to standalone (#51516)
None of the guide pages mentions ngModules. Only `observables-in-angular` needed conversion to Standalone.

However, some of the guide pages reflect old versions of RxJS, including signatures that are no longer valid. These have been corrected.

More significantly, *the existing guide is pretty bad at explaining RxJS and its usage*. It was written (by me I think) in the very early days of Angular and Angular RxJS instruction. I've taught numerous "RxJS in Angular" classes since and learned from that experience what does and does not work with students.

There was neither the time nor the charter to completely overhaul this guide. But this commit attempts to remove what flops with students and to bring the teaching closer to what seems more effectively. I hope reviewers agree that my revisions are an improvement.

**Revised Overview**

The overview doc, `observables.md`, had a few errors (ex: `next` is NOT REQUIRED) and deprecated patterns (you now must pass the Observer object to `subscribe`).

More importantly, it was wildly overcomplicated and scary, especially when it got into multi-casting.

Moved the multi-casting section to  "RxJS Library" and rewrote it (with working example) for simplicity and context.

I made other changes in an effort to make this an overview that is  more comprehensive and more clear. I paid particular attention to the "Basic usage and terms" section.

Finally, I relocated the "Naming conventions for observables" section here from `rx-library`. This is the section that describes the dollar-sign convention. It made more sense for it to be here.

**Revised "RxJS Library" page and code**

*RxJS no longer supports chaining* and hasn't for a very long time. Removed note in `rx-library.md` that suggested you could use operator chaining.

The RxJS `pipe` discussion in the "Operators" section was just weird. Almost no one calls the `pipe` function. We certainly should *start* there. We should start with how people actually use operators - by adding them to the argument list of the `Observable.pipe()` method.

I kept the original `pipe` function example but subordinated it in a "callout". Most readers will (and should) ignore it.

`Subject` is a *critically important RxJS mechanism for creating custom observables*. It was completely missing from the list of observable creators on this guide page. So I added it to the "Observable creation functions" section of the guide and wrote an accompanying `MessageService` code sample (see the new `rx-library/app/` folder).

The `MessageService` is a pretty common pattern in Angular apps - far more common than creating an observable from a counter or an event, two of the creation patterns currently on this page.

This new section also afforded an opportunity to show how RxJS helps with building loosely coupled applications. We will soon be talking about Signals. Many will wonder whether and when they should still use RxJS.

At least a partial answer is that RxJS is really good at progressively combining and enhancing streams of data as they cross component boundaries. Of course you can pass signals around; but they are not as rich in transformers as RxJS. This is where RxJS shines.

**Revised "Comparing observables"**

The Promises section in `comparing-observables.md` had many errors and misleading remarks.

The comparison of error handling was especially egregious; the code example for that was nonsense.

The "Chain" sub-section was really about transforming values. It also failed to demonstrate chaining promise `.then`s.

Reworked these sub-sections and improved the code samples to match.

PR Close #51516
2023-08-31 17:00:46 +00:00

15 KiB

Observables compared to other techniques

You can often use observables instead of promises to deliver values asynchronously. Similarly, observables can take the place of event handlers. Finally, because observables deliver multiple values, you can use them where you might otherwise build and operate on arrays.

Observables behave somewhat differently from the alternative techniques in each of these situations, but offer some significant advantages. Here are detailed comparisons of the differences.

Observables compared to promises

Observables are often compared to promises. Here are some key differences:

  • Observable execution is deferred; computation does not start until subscription. Promises execute immediately on creation. This makes observables useful for defining recipes that can be run whenever you need the result.

  • Observables provide many values. Promises provide one. This makes observables useful for getting multiple values over time.

  • Observable values can be transformed with operators as well as in the subscription. The rich variety of RxJS operators observables enables complex transformations that can be passed around to other parts of the system, without causing the work to be executed prematurely.

    Promises have .then() clauses which can transform values but only after the work is done.

  • Observables and Promises handle errors differently with roughly comparable efficacy.

The following sections explore these points in greater detail.

Creation and subscription

  • Observables are not executed until a consumer subscribes.

    The subscribe() initiates the observable's behavior which may execute synchronously or asynchronously and could produce one, many or no values over time.

    For "unicast" observables, if you call subscribe again, you get a new observable execution with its own production of values. Calling subscribe on a "multicast" observable (e.g., Subject or an observable with the shareReplay operator) simply adds another subscriber to the already running observable.

    The subscribe call is the end-of-the-line. You cannot continue to manipulate values after subscribe(...).

  • Promises execute immediately when they are created. There is no deferred execution and, therefore, no equivalent to subscribe().

    A promise is always asynchronous and can produce at most one value.

    There is no way to restart a promise and it retains its result value for the life of the promise.

    You can chain additional then clauses to a promise.

Transformations

  • Developers can transform values both in the subscription and in piped operators. There are large number of RxJS operators to suit many complex scenarios, including numerous ways to combine and split observables.

  • Promises do not have an equivalent to subscribe(). You can transform the emitted value of a promise through one or more .then clauses. Promises have a small set of combiners (e.g., all, any, race).

Cancellation

  • Observable subscriptions are cancellable. Unsubscribing removes the listener from receiving further values, and notifies the subscriber function to cancel work.

  • Promises are not cancellable.

Error handling

  • Observable execution errors can be handled with the catchError() operator or in the subscribe.

    catchError can put the observable back on the normal path where it continues to produce values or it can rethrow the error. An uncaught error unsubscribes all subscribers.

  • Promise errors can be handled with a .catch() or in the second argument of a .then().

Cheat sheet

The following code snippets illustrate how the same kind of operation is defined using observables and promises.

Operation Observable Promise
Creation new Observable((observer) => {   observer.next(123); }); new Promise((resolve, reject) => {   resolve(123); });
Transform obs.pipe(map((value) => value * 2)); promise.then((value) => value * 2);
Subscribe sub = obs.subscribe((value) => {   console.log(value) }); promise.then((value) => {   console.log(value); });
Unsubscribe sub.unsubscribe(); Implied by promise resolution.

Observables compared to events API

Observables are very similar to event handlers that use the events API. Both techniques define notification handlers, and use them to process multiple values delivered over time. Subscribing to an observable is equivalent to adding an event listener. One significant difference is that you can configure an observable to transform an event before passing the event to the handler.

Using observables to handle events and asynchronous operations can have the advantage of greater consistency in contexts such as HTTP requests.

Here are some code samples that illustrate how the same kind of operation is defined using observables and the events API.

Observable Events API
Creation & cancellation // Setup const clicks$ = fromEvent(buttonEl, 'click'); // Begin listening const subscription = clicks$   .subscribe(e => console.log('Clicked', e)) // Stop listening subscription.unsubscribe(); function handler(e) {   console.log('Clicked', e); } // Setup & begin listening button.addEventListener('click', handler); // Stop listening button.removeEventListener('click', handler);
Subscription observable.subscribe(() => {   // notification handlers here }); element.addEventListener(eventName, (event) => {   // notification handler here });
Configuration Listen for keystrokes, but provide a stream representing the value in the input. fromEvent(inputEl, 'keydown').pipe(   map(e => e.target.value) ); Does not support configuration. element.addEventListener(eventName, (event) => {   // Cannot change the passed Event into another   // value before it gets to the handler });

Observables compared to arrays

An observable produces values over time. An array is created as a static set of values. In a sense, observables are asynchronous where arrays are synchronous. In the following examples, implies asynchronous value delivery.

Values Observable Array
Given obs: →1→2→3→5→7 obsB: →'a'→'b'→'c' arr: [1, 2, 3, 5, 7] arrB: ['a', 'b', 'c']
concat() concat(obs, obsB) →1→2→3→5→7→'a'→'b'→'c' arr.concat(arrB) [1,2,3,5,7,'a','b','c']
filter() obs.pipe(filter((v) => v>3)) →5→7 arr.filter((v) => v>3) [5, 7]
find() obs.pipe(find((v) => v>3)) →5 arr.find((v) => v>3) 5
findIndex() obs.pipe(findIndex((v) => v>3)) →3 arr.findIndex((v) => v>3) 3
forEach() obs.pipe(tap((v) => {   console.log(v); })) 1 2 3 5 7 arr.forEach((v) => {   console.log(v); }) 1 2 3 5 7
map() obs.pipe(map((v) => -v)) →-1→-2→-3→-5→-7 arr.map((v) => -v) [-1, -2, -3, -5, -7]
reduce() obs.pipe(reduce((s,v)=> s+v, 0)) →18 arr.reduce((s,v) => s+v, 0) 18

@reviewed 2023-08-25