|
| 1 | +block includes |
| 2 | + include ../_util-fns |
| 3 | + |
| 4 | +:marked |
| 5 | + **Observables** provided by the Reactive Extensions for Javascript (RxJS) library provide applications with an extensive API |
| 6 | + for handling asynchronous and event-based values produced over time. |
| 7 | + |
| 8 | + An application is made up of many different streams of information. Whether it be user input into |
| 9 | + a form, navigating from one route another, making an HTTP request to fetch some data, updating the application view with |
| 10 | + new data as its received, or many other examples, each of these events happen over time. Observables provide a interface to |
| 11 | + handle the many different sources of events and help to transform these events as they flow throughout an application. |
| 12 | + |
| 13 | + This guide will serve as an introductory chapter to Observables, common uses cases for Observables in an Angular application |
| 14 | + and how Observables are used and provided by the Angular framework. |
| 15 | + |
| 16 | + ## Table of Contents |
| 17 | + * [The Observable](#definition "") |
| 18 | + * [Observables and Promises](#promises "") |
| 19 | + * [Observable Lifecycle](#lifecycle "") |
| 20 | + * [Observer](#lifecycle "") |
| 21 | + * [Subscriptions](#subscriptions "") |
| 22 | + * [Creating Observables](#creating "") |
| 23 | + * [Operators](#transforming "") |
| 24 | + * [Error Handling](#error-handling "") |
| 25 | + * [Framework APIs](#apis "") |
| 26 | + * [Impact on Change Detection](#change-detection "") |
| 27 | + * [Further Reading](#reading "") |
| 28 | + |
| 29 | +:marked |
| 30 | + The Observable: a function at its core |
| 31 | + |
| 32 | + An Observable, simply put, is a specific type of function with a specific purpose. Its a function that accepts an `Observer` to produce values and |
| 33 | + returns a function for cancellation. It represents an action that can be performed. This action may be performed right now, or at some point |
| 34 | + in the future. |
| 35 | + |
| 36 | + An action can be anything, from simply "return a constant" to "make an HTTP request". Here’s a simple "action" function that increments a counter |
| 37 | + and returns the new value. |
| 38 | + |
| 39 | + // Example of simple counter |
| 40 | + |
| 41 | + The same functionality can be produced with an Observable. Observables don't return values directly, as they can be produced synchronously asynchronously. |
| 42 | + An Observer is used by an Observable to consume its produced values. |
| 43 | + |
| 44 | + // Example of observable counter |
| 45 | + |
| 46 | + There is a key difference between these two examples. In the first counter example, the results of the action were produced when the function was called. |
| 47 | + In the Observable counter, the Observable was created, with the [Observer](https://en.wikipedia.org/wiki/Observer_pattern) to produce values and the value incremented but the action hasn't been performed yet. |
| 48 | + It represents an action we've defined, but it hasn't been executed. |
| 49 | + |
| 50 | + // Example of subscribing to observable counter |
| 51 | + |
| 52 | + Observable streams are **cold** or **lazy** by nature, meaning that until you invoke them, they will not produce |
| 53 | + any values. Invoking an Observable is done using the `subscribe` method, which makes the Observable **hot** and calls the observer's method to produce |
| 54 | + values. The `subscribe` function also returns an object with an `unsubscribe` from an Observable and no longer receive the values it produces. |
| 55 | + |
| 56 | + // Example of unsubscribing |
| 57 | + |
| 58 | + Managing the lifecycle of an Observable will be discussed later in the chapter. |
| 59 | + |
| 60 | +:marked |
| 61 | + Observables and Promises: More different than alike |
| 62 | + |
| 63 | + RxJS and Observables have been around for a long time, and they aren't the first concept of handling asynchronous events. Before Observables became more prevalent, |
| 64 | + the `Promise` was the primary way of handling asynchronous events. Promises and Observables share some similarities as they both handle asynchronous events, |
| 65 | + both implement a function to handle execution and error handling, but they are more different then alike. |
| 66 | + |
| 67 | + Promises |
| 68 | + * Produce a one-time value |
| 69 | + * Can be composed |
| 70 | + * Are always resolved/rejected asynchronously |
| 71 | + * Are always multicast to multiple receivers |
| 72 | + |
| 73 | + Observables |
| 74 | + * Produce a number of values over time |
| 75 | + * Can be composed |
| 76 | + * Resolve synchronously/asynchronously |
| 77 | + * Multicast when needed |
| 78 | + |
| 79 | + The strength of Observables is producing and handling values over time, which is something a Promise wasn't designed to do. Observables also provide mechanisms |
| 80 | + for easy cancellation, retrying upon failure and transformations. Observables include a rich library of operators, along with the extensibility to provide a more powerful |
| 81 | + tool to handle the various streams of events in an application. So does this mean Promises are no longer needed? Absolutely not. Promises will continue to serve a purpose as the right tool for the job in some situations. |
| 82 | + The good news is Observables support conversion to a Promise for instances where a one-off value needs to be produced without managing the lifecycle |
| 83 | + of an Observable. |
| 84 | + |
| 85 | +:marked |
| 86 | + Observable Anatomy: Next, Error, and Complete |
| 87 | + |
| 88 | + Even though an Observable is a function that performs an action, it has a lifecycle. An Observable has 3 types of notifications it produces through its |
| 89 | + lifetime: `next`, `error`, and `complete`. |
| 90 | + |
| 91 | + The `next` is called whenever the observable produces a new value, notifying the Observer |
| 92 | + that some value was produced. The `next` Observables may produce a single value, or multiple values over time. These values can be a number, string, object |
| 93 | + |
| 94 | + |
| 95 | + Let's modify the HeroesReadyComponent to produce two values when subscribed. |
| 96 | + |
| 97 | + // Example of Observable that produces 2 values |
| 98 | + |
| 99 | + When click the `increment` button this time and subscribes to the Observable, the counter will be incremented twice and produce two values. |
| 100 | + |
| 101 | + Whenever an Observable produces an error, it uses the `error` event. An error event can only happen once during the invocation of an Observable action. |
| 102 | + |
| 103 | + // Example of Observable that errors |
| 104 | + |
| 105 | + The Observable notifies its Observer through that an error has occurred. |
| 106 | + |
| 107 | + Observables can also notify observers of completion. A completion event signals that no more values will be produced by the Observable. Like the error event, |
| 108 | + a completion event can only happen once. |
| 109 | + |
| 110 | + // Example of Observable that produces values, then completes |
| 111 | + |
| 112 | +:marked |
| 113 | + Observer: The Observable's Consumer |
| 114 | + An Observer is provided to an Observable to consume its values produced. An observer provides callbacks for the notification types produced by an Observable: `next`, |
| 115 | + `error`, and `complete`. |
| 116 | + |
| 117 | + An Observer is provided to an Observable through the `subscribe` method in 2 ways |
| 118 | + |
| 119 | + * As a single object containing 3 callbacks for each notification. |
| 120 | + * As 3 arguments in the subscribe method for each notification |
| 121 | + |
| 122 | + // Example of single object and logging out each notification |
| 123 | + |
| 124 | + // Example of 3 arguments to subscribe method |
| 125 | + |
| 126 | +:marked |
| 127 | + Subscription: Maintaining of resources |
| 128 | + |
| 129 | + As mentioned earlier, an Observable is not invoked until its `subscribed` to. This starts the execution of the Observable to produce values or events to an Observer. |
| 130 | + This subscription is an allocation of resources for the action performed by the Observable. Naturally, you want to clean up resources used by the Observable when |
| 131 | + finished with its execution. Each time an Observable is subscribed, it returns a `Subscription`. The `Subscription` is an object that handles the resources provided |
| 132 | + by the Observable, along with the `unsubscribe`. The `unsubscribe` method provided by the `Subscription` disposes of the resources allocated by the Observable. |
| 133 | + |
| 134 | + // Example of unsubscribing |
| 135 | + |
| 136 | + As a general rule and good practice, resources that are allocated and used must be cleaned up, and the Observable subscription is no different. Angular provides |
| 137 | + APIs that manage Observable lifecycles without the need to unsubscribe, but for those Observables you create, cleanup of resources is a must to protect against |
| 138 | + memory leaks and other unwanted side effects. |
| 139 | + |
| 140 | + // Example of unsubscribe in ngOnDestroy |
| 141 | + |
| 142 | + In this example, only one subscription is used and disposed of. Managing multiple subscriptions using the `unsubscribe` method |
| 143 | + has the potential to get unwieldy. You'll learn about different ways to to end subscriptions of multiple Observables later in the chapter. |
| 144 | + |
| 145 | + // Example of unsubscribe using a Subject |
| 146 | + |
| 147 | +:marked |
| 148 | + Operators: Observable functions |
| 149 | + |
| 150 | + The Observable prototype contains many `operators`, which are methods that perform an operation. Operators are **pure functions** |
| 151 | + that are stateless. Stateless operators are less error-prone as they are provided an Observable, in which an operation is performed on, |
| 152 | + and return a new Observable, without modifying the original Observable. The Observable prototype comes with a minimal set of operators by default, |
| 153 | + with a large set of operators that can be added. Operators are also used to (create Observable instances) from existing events. |
| 154 | + |
| 155 | + There are different ways to access the operators provided by RxJS: By patching the Observable prototype or by importing the operator functions directly. |
| 156 | + Each operator only needs to be patched on the prototype once, but where you choose to patch the Observable requires consideration. We'll examine a |
| 157 | + few options below: |
| 158 | + |
| 159 | + // Example of importing entire rxjs/Rx library |
| 160 | + |
| 161 | + By importing `rxjs/Rx`, the **entire** set of RxJS operators are added to the Observable prototype. In most cases, you will only use a subset of |
| 162 | + the available operators and adding the entire set increases the overall bundle size of your application. This is |
| 163 | + only recommended for sample apps, and in testing environments. |
| 164 | + |
| 165 | + // Example of patching the Observable prototype |
| 166 | + |
| 167 | + Since minimizing bundle size is a recommended practice, you should only import the operators you need, where you need them. |
| 168 | + This approach includes importing operators multiple times in different files, but safeguards against using operators |
| 169 | + without having patched the Observable prototype first. Since feature areas can be loaded lazily, this also allows you the benefit |
| 170 | + of keeping certain operators in separate bundles and only loaded them when needed. |
| 171 | + |
| 172 | + // Example of importing operators directly |
| 173 | + |
| 174 | + // Note about tree-shaking when pat |
| 175 | + |
| 176 | + NOTE: Operators patched onto the Observable prototype that are unused will not be tree-shaken. |
| 177 | + |
| 178 | + Operators can also be directly imported and used without patching the Observable prototype. This is the |
| 179 | + recommended option for third-party libraries, staying with the approach to only import what you need. It |
| 180 | + is also more suited for tree-shaking when bundling your Angular application. |
| 181 | + |
| 182 | +:marked |
| 183 | + |
| 184 | + * Pure functions |
| 185 | + |
| 186 | + Subscribe |
| 187 | + Produce Values |
| 188 | + Unsubscribe |
| 189 | + Complete |
| 190 | + |
| 191 | + Observer |
| 192 | + - Provided to an Observable |
| 193 | + - Next, Error, Complete callbacks |
| 194 | + - Next to produce values |
| 195 | + - Error when errors occur |
| 196 | + - Complete when observable completes |
| 197 | + |
| 198 | + Subscription |
| 199 | + - Function to release resources allocated to Observable |
| 200 | + - Returns object with unsubscribe to clean up |
| 201 | + - ngOnDestroy vs takeUntil |
| 202 | + |
| 203 | + Creating Observables |
| 204 | + - From events |
| 205 | + - From a Promise |
| 206 | + - From multiple observables |
| 207 | + - Example: Create observable from input on blur event |
| 208 | + |
| 209 | + Subject |
| 210 | + - Is an Observable |
| 211 | + - Keeps an internal list of subscribers |
| 212 | + - Multicasting to multiple receivers |
| 213 | + - Observable unicast to one receiver for execution |
| 214 | + - Example: Counter using a subject |
| 215 | + |
| 216 | + BehaviorSubject |
| 217 | + - Is an Observable |
| 218 | + - Keeps a memory of the current value |
| 219 | + - Each subscriber gets the same value |
| 220 | + - Example: EventAggregator Service |
| 221 | + |
| 222 | + Operators |
| 223 | + - Stateless |
| 224 | + - Pure |
| 225 | + - Less error prone |
| 226 | + - Chainable |
| 227 | + - Fluent |
| 228 | + - Import all vs Direct Import vs Observable patch |
| 229 | + |
| 230 | +:marked |
| 231 | + RxJS in Angular Framework |
| 232 | + |
| 233 | + - Uses Observables internally |
| 234 | + - Only uses what's needed |
| 235 | + - Imports operators |
| 236 | + - Exposes Reactive APIs |
| 237 | + - Async Pipe |
| 238 | + - Reactive Forms |
| 239 | + - HTTP |
| 240 | + - Router |
| 241 | + - Example: Hero Search Typeahead |
| 242 | + |
| 243 | + |
| 244 | +Goals |
| 245 | + |
| 246 | + * Provide simple definition of what an Observable is. |
| 247 | + * Talk about the predecessor to Observables - The Promise |
| 248 | + * Why Observables are more prevalent. |
| 249 | + * Also support conversion to a promise. |
| 250 | + |
| 251 | +:marked |
| 252 | + Creating Observables |
| 253 | + cold/hot |
| 254 | + subscribe - next/error/complete |
| 255 | + unsubscribe |
| 256 | + |
| 257 | + * Observable |
| 258 | + * Subject |
| 259 | + * BehaviorSubject |
| 260 | + |
| 261 | +:marked |
| 262 | + Handling Lifecycle |
| 263 | + Subscription |
| 264 | + takeUntil |
| 265 | + |
| 266 | +:marked |
| 267 | + Operators: Transforming Observables |
| 268 | + Import entire lib/Import where used/rxjs-extensions file |
| 269 | + Observable patch prototype vs direct import |
| 270 | + - chaining |
| 271 | + - tree shaking |
| 272 | + |
| 273 | +:marked |
| 274 | + Creation Operators |
| 275 | + * fromEvent |
| 276 | + * of |
| 277 | + |
| 278 | +:marked |
| 279 | + Transforming Operators |
| 280 | + * map |
| 281 | + * (flat)mergeMap |
| 282 | + * switchMap |
| 283 | + * toPromise |
| 284 | + * share |
| 285 | + |
| 286 | +:marked |
| 287 | + Filtering operators |
| 288 | + * distinctUntilChanged |
| 289 | + * filter |
| 290 | + * debounceTime |
| 291 | + |
| 292 | +:marked |
| 293 | + Utility operators |
| 294 | + * do |
| 295 | + * delay |
| 296 | + |
| 297 | +:marked |
| 298 | + Error Handling |
| 299 | + |
| 300 | + * catch |
| 301 | + * retry |
| 302 | + |
| 303 | +:marked |
| 304 | + Sharing data |
| 305 | + |
| 306 | + * Event Aggregator service |
| 307 | + * share value between all subscribers |
| 308 | + |
| 309 | +:marked |
| 310 | + Observables within Angular |
| 311 | + |
| 312 | + Only makes use of minimal set |
| 313 | + |
| 314 | +:marked |
| 315 | + Framework APIs |
| 316 | + * Outputs (Event Emitter) |
| 317 | + * Forms (Reactive Forms) |
| 318 | + * valueChanges |
| 319 | + * distinctUntilChanged |
| 320 | + * debounceTime |
| 321 | + |
| 322 | + * Async Pipe (Handles template subscriptions) |
| 323 | + * switchMap |
| 324 | + |
| 325 | + * Router (Parameters, Events) |
| 326 | + * loaded |
| 327 | + |
| 328 | + * Http (All request methods) |
| 329 | + * toPromise |
| 330 | + * catch |
| 331 | + * retry |
| 332 | + |
| 333 | +:marked |
| 334 | + Impact on Change Detection |
| 335 | + |
| 336 | + * Use with Inputs |
| 337 | + * Change detection triggered |
| 338 | + * OnPush ChangeDetectionStrategy |
| 339 | + |
| 340 | +:marked |
| 341 | + Further Reading |
| 342 | + |
| 343 | + * Official Docs |
0 commit comments