5 lines
21 KiB
JSON
5 lines
21 KiB
JSON
{
|
||
"id": "guide/observables",
|
||
"title": "Using observables to pass values",
|
||
"contents": "\n\n\n<div class=\"github-links\">\n <a href=\"https://github.com/angular/angular/edit/master/aio/content/guide/observables.md?message=docs%3A%20describe%20your%20change...\" aria-label=\"Suggest Edits\" title=\"Suggest Edits\"><i class=\"material-icons\" aria-hidden=\"true\" role=\"img\">mode_edit</i></a>\n</div>\n\n\n<div class=\"content\">\n <h1 id=\"using-observables-to-pass-values\">Using observables to pass values<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/observables#using-observables-to-pass-values\"><i class=\"material-icons\">link</i></a></h1>\n<p>Observables provide support for passing messages between parts of your application.\nThey are used frequently in Angular and are a technique for event handling, asynchronous programming, and handling multiple values.</p>\n<p>The observer pattern is a software design pattern in which an object, called the <em>subject</em>, maintains a list of its dependents, called <em>observers</em>, and notifies them automatically of state changes.\nThis pattern is similar (but not identical) to the <a href=\"https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern\">publish/subscribe</a> design pattern.</p>\n<p>Observables are declarative—that is, you define a function for publishing values, but it is not executed until a consumer subscribes to it.\nThe subscribed consumer then receives notifications until the function completes, or until they unsubscribe.</p>\n<p>An observable can deliver multiple values of any type—literals, messages, or events, depending on the context. The API for receiving values is the same whether the values are delivered synchronously or asynchronously. Because setup and teardown logic are both handled by the observable, your application code only needs to worry about subscribing to consume values, and when done, unsubscribing. Whether the stream was keystrokes, an HTTP response, or an interval timer, the interface for listening to values and stopping listening is the same.</p>\n<p>Because of these advantages, observables are used extensively within Angular, and for app development as well.</p>\n<h2 id=\"basic-usage-and-terms\">Basic usage and terms<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/observables#basic-usage-and-terms\"><i class=\"material-icons\">link</i></a></h2>\n<p>As a publisher, you create an <code>Observable</code> instance that defines a <em>subscriber</em> function. This is the function that is executed when a consumer calls the <code>subscribe()</code> method. The subscriber function defines how to obtain or generate values or messages to be published.</p>\n<p>To execute the observable you have created and begin receiving notifications, you call its <code>subscribe()</code> method, passing an <em>observer</em>. This is a JavaScript object that defines the handlers for the notifications you receive. The <code>subscribe()</code> call returns a <code>Subscription</code> object that has an <code>unsubscribe()</code> method, which you call to stop receiving notifications.</p>\n<p>Here's an example that demonstrates the basic usage model by showing how an observable could be used to provide geolocation updates.</p>\n<code-example class=\"no-auto-link\" path=\"observables/src/geolocation.ts\" header=\"Observe geolocation updates\">\n\n// Create an Observable that will start listening to geolocation updates\n// when a consumer subscribes.\nconst locations = new Observable((observer) => {\n let watchId: number;\n\n // Simple geolocation API check provides values to publish\n if ('geolocation' in navigator) {\n watchId = navigator.geolocation.watchPosition((position: Position) => {\n observer.next(position);\n }, (error: PositionError) => {\n observer.error(error);\n });\n } else {\n observer.error('Geolocation not available');\n }\n\n // When the consumer unsubscribes, clean up data ready for next subscription.\n return {\n unsubscribe() {\n navigator.geolocation.clearWatch(watchId);\n }\n };\n});\n\n// Call subscribe() to start listening for updates.\nconst locationsSubscription = locations.subscribe({\n next(position) {\n console.log('Current Position: ', position);\n },\n error(msg) {\n console.log('Error Getting Location: ', msg);\n }\n});\n\n// Stop listening for location after 10 seconds\nsetTimeout(() => {\n locationsSubscription.unsubscribe();\n}, 10000);\n\n</code-example>\n<h2 id=\"defining-observers\">Defining observers<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/observables#defining-observers\"><i class=\"material-icons\">link</i></a></h2>\n<p>A handler for receiving observable notifications implements the <code>Observer</code> interface. It is an object that defines callback methods to handle the three types of notifications that an observable can send:</p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Notification type</th>\n<th align=\"left\">Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\"><code>next</code></td>\n<td align=\"left\">Required. A handler for each delivered value. Called zero or more times after execution starts.</td>\n</tr>\n<tr>\n<td align=\"left\"><code>error</code></td>\n<td align=\"left\">Optional. A handler for an error notification. An error halts execution of the observable instance.</td>\n</tr>\n<tr>\n<td align=\"left\"><code>complete</code></td>\n<td align=\"left\">Optional. A handler for the execution-complete notification. Delayed values can continue to be delivered to the next handler after execution is complete.</td>\n</tr>\n</tbody>\n</table>\n<p>An observer object can define any combination of these handlers. If you don't supply a handler for a notification type, the observer ignores notifications of that type.</p>\n<h2 id=\"subscribing\">Subscribing<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/observables#subscribing\"><i class=\"material-icons\">link</i></a></h2>\n<p>An <code>Observable</code> instance begins publishing values only when someone subscribes to it. You subscribe by calling the <code>subscribe()</code> method of the instance, passing an observer object to receive the notifications.</p>\n<div class=\"alert is-helpful\">\n<p>In order to show how subscribing works, we need to create a new observable. There is a constructor that you use to create new instances, but for illustration, we can use some methods from the RxJS library that create simple observables of frequently used types:</p>\n<ul>\n<li><code>of(...items)</code>—Returns an <code>Observable</code> instance that synchronously delivers the values provided as arguments.</li>\n<li><code>from(iterable)</code>—Converts its argument to an <code>Observable</code> instance. This method is commonly used to convert an array to an observable.</li>\n</ul>\n</div>\n<p>Here's an example of creating and subscribing to a simple observable, with an observer that logs the received message to the console:</p>\n<code-example path=\"observables/src/subscribing.ts\" region=\"observer\" header=\"Subscribe using observer\">\n\n// Create simple observable that emits three values\nconst myObservable = of(1, 2, 3);\n\n// Create observer object\nconst myObserver = {\n next: x => console.log('Observer got a next value: ' + x),\n error: err => console.error('Observer got an error: ' + err),\n complete: () => console.log('Observer got a complete notification'),\n};\n\n// Execute with the observer object\nmyObservable.subscribe(myObserver);\n\n// Logs:\n// Observer got a next value: 1\n// Observer got a next value: 2\n// Observer got a next value: 3\n// Observer got a complete notification\n\n\n</code-example>\n<p>Alternatively, the <code>subscribe()</code> method can accept callback function definitions in line, for <code>next</code>, <code>error</code>, and <code>complete</code> handlers. For example, the following <code>subscribe()</code> call is the same as the one that specifies the predefined observer:</p>\n<code-example path=\"observables/src/subscribing.ts\" region=\"sub_fn\" header=\"Subscribe with positional arguments\">\nmyObservable.subscribe(\n x => console.log('Observer got a next value: ' + x),\n err => console.error('Observer got an error: ' + err),\n () => console.log('Observer got a complete notification')\n);\n\n</code-example>\n<p>In either case, a <code>next</code> handler is required. The <code>error</code> and <code>complete</code> handlers are optional.</p>\n<p>Note that a <code>next()</code> function could receive, for instance, message strings, or event objects, numeric values, or structures, depending on context. As a general term, we refer to data published by an observable as a <em>stream</em>. Any type of value can be represented with an observable, and the values are published as a stream.</p>\n<h2 id=\"creating-observables\">Creating observables<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/observables#creating-observables\"><i class=\"material-icons\">link</i></a></h2>\n<p>Use the <code>Observable</code> constructor to create an observable stream of any type. The constructor takes as its argument the subscriber function to run when the observable’s <code>subscribe()</code> method executes. A subscriber function receives an <code>Observer</code> object, and can publish values to the observer's <code>next()</code> method.</p>\n<p>For example, to create an observable equivalent to the <code>of(1, 2, 3)</code> above, you could do something like this:</p>\n<code-example path=\"observables/src/creating.ts\" region=\"subscriber\" header=\"Create observable with constructor\">\n// This function runs when subscribe() is called\nfunction sequenceSubscriber(observer) {\n // synchronously deliver 1, 2, and 3, then complete\n observer.next(1);\n observer.next(2);\n observer.next(3);\n observer.complete();\n\n // unsubscribe function doesn't need to do anything in this\n // because values are delivered synchronously\n return {unsubscribe() {}};\n}\n\n// Create a new Observable that will deliver the above <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>\nconst <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> = new Observable(sequenceSubscriber);\n\n// execute the Observable and print the result of each notification\nsequence.subscribe({\n next(num) { console.log(num); },\n complete() { console.log('Finished <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>'); }\n});\n\n// Logs:\n// 1\n// 2\n// 3\n// Finished <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>\n\n</code-example>\n<p>To take this example a little further, we can create an observable that publishes events. In this example, the subscriber function is defined inline.</p>\n<code-example path=\"observables/src/creating.ts\" region=\"fromevent\" header=\"Create with custom fromEvent function\">\n\nfunction fromEvent(target, eventName) {\n return new Observable((observer) => {\n const handler = (e) => observer.next(e);\n\n // Add the event handler to the target\n target.addEventListener(eventName, handler);\n\n return () => {\n // Detach the event handler from the target\n target.removeEventListener(eventName, handler);\n };\n });\n}\n\n\n</code-example>\n<p>Now you can use this function to create an observable that publishes keydown events:</p>\n<code-example path=\"observables/src/creating.ts\" region=\"fromevent_use\" header=\"Use custom fromEvent function\">\n\nconst ESC_KEY = 27;\nconst nameInput = document.getElementById('name') as HTMLInputElement;\n\nconst subscription = fromEvent(nameInput, 'keydown').subscribe((e: KeyboardEvent) => {\n if (e.keyCode === ESC_KEY) {\n nameInput.value = '';\n }\n});\n\n</code-example>\n<h2 id=\"multicasting\">Multicasting<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/observables#multicasting\"><i class=\"material-icons\">link</i></a></h2>\n<p>A typical observable creates a new, independent execution for each subscribed observer. When an observer subscribes, the observable wires up an event handler and delivers values to that observer. When a second observer subscribes, the observable then wires up a new event handler and delivers values to that second observer in a separate execution.</p>\n<p>Sometimes, instead of starting an independent execution for each subscriber, you want each subscription to get the same values—even if values have already started emitting. This might be the case with something like an observable of clicks on the document object.</p>\n<p><em>Multicasting</em> is the practice of broadcasting to a list of multiple subscribers in a single execution. With a multicasting observable, you don't register multiple listeners on the document, but instead re-use the first listener and send values out to each subscriber.</p>\n<p>When creating an observable you should determine how you want that observable to be used and whether or not you want to multicast its values.</p>\n<p>Let’s look at an example that counts from 1 to 3, with a one-second delay after each number emitted.</p>\n<code-example path=\"observables/src/multicasting.ts\" region=\"delay_sequence\" header=\"Create a delayed sequence\">\nfunction sequenceSubscriber(observer) {\n const seq = [1, 2, 3];\n let timeoutId;\n\n // Will run through an array of numbers, emitting one value\n // per second until it gets to the end of the array.\n function doInSequence(arr, idx) {\n timeoutId = setTimeout(() => {\n observer.next(arr[idx]);\n if (idx === arr.length - 1) {\n observer.complete();\n } else {\n doInSequence(arr, ++idx);\n }\n }, 1000);\n }\n\n doInSequence(seq, 0);\n\n // Unsubscribe should clear the timeout to stop execution\n return {\n unsubscribe() {\n clearTimeout(timeoutId);\n }\n };\n}\n\n// Create a new Observable that will deliver the above <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>\nconst <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> = new Observable(sequenceSubscriber);\n\nsequence.subscribe({\n next(num) { console.log(num); },\n complete() { console.log('Finished <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>'); }\n});\n\n// Logs:\n// (at 1 second): 1\n// (at 2 seconds): 2\n// (at 3 seconds): 3\n// (at 3 seconds): Finished <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>\n\n\n</code-example>\n<p>Notice that if you subscribe twice, there will be two separate streams, each emitting values every second. It looks something like this:</p>\n<code-example path=\"observables/src/multicasting.ts\" region=\"subscribe_twice\" header=\"Two subscriptions\">\n\n// Subscribe starts the clock, and will emit after 1 second\nsequence.subscribe({\n next(num) { console.log('1st subscribe: ' + num); },\n complete() { console.log('1st <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished.'); }\n});\n\n// After 1/2 second, subscribe again.\nsetTimeout(() => {\n sequence.subscribe({\n next(num) { console.log('2nd subscribe: ' + num); },\n complete() { console.log('2nd <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished.'); }\n });\n}, 500);\n\n// Logs:\n// (at 1 second): 1st subscribe: 1\n// (at 1.5 seconds): 2nd subscribe: 1\n// (at 2 seconds): 1st subscribe: 2\n// (at 2.5 seconds): 2nd subscribe: 2\n// (at 3 seconds): 1st subscribe: 3\n// (at 3 seconds): 1st <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished\n// (at 3.5 seconds): 2nd subscribe: 3\n// (at 3.5 seconds): 2nd <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished\n\n\n</code-example>\n<p> Changing the observable to be multicasting could look something like this:</p>\n<code-example path=\"observables/src/multicasting.ts\" region=\"multicast_sequence\" header=\"Create a multicast subscriber\">\nfunction multicastSequenceSubscriber() {\n const seq = [1, 2, 3];\n // Keep track of each observer (one for every active subscription)\n const observers = [];\n // Still a single timeoutId because there will only ever be one\n // set of values being generated, multicasted to each subscriber\n let timeoutId;\n\n // Return the subscriber function (runs when subscribe()\n // function is invoked)\n return observer => {\n observers.push(observer);\n // When this is the first subscription, start the <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>\n if (observers.length === 1) {\n timeoutId = doSequence({\n next(val) {\n // Iterate through observers and notify all subscriptions\n observers.forEach(obs => obs.next(val));\n },\n complete() {\n // Notify all complete callbacks\n observers.slice(0).forEach(obs => obs.complete());\n }\n }, seq, 0);\n }\n\n return {\n unsubscribe() {\n // Remove from the observers array so it's no longer notified\n observers.splice(observers.indexOf(observer), 1);\n // If there's no more listeners, do cleanup\n if (observers.length === 0) {\n clearTimeout(timeoutId);\n }\n }\n };\n };\n}\n\n// Run through an array of numbers, emitting one value\n// per second until it gets to the end of the array.\nfunction doSequence(observer, arr, idx) {\n return setTimeout(() => {\n observer.next(arr[idx]);\n if (idx === arr.length - 1) {\n observer.complete();\n } else {\n doSequence(observer, arr, ++idx);\n }\n }, 1000);\n}\n\n// Create a new Observable that will deliver the above <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a>\nconst multicastSequence = new Observable(multicastSequenceSubscriber());\n\n// Subscribe starts the clock, and begins to emit after 1 second\nmulticastSequence.subscribe({\n next(num) { console.log('1st subscribe: ' + num); },\n complete() { console.log('1st <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished.'); }\n});\n\n// After 1 1/2 seconds, subscribe again (should \"miss\" the first value).\nsetTimeout(() => {\n multicastSequence.subscribe({\n next(num) { console.log('2nd subscribe: ' + num); },\n complete() { console.log('2nd <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished.'); }\n });\n}, 1500);\n\n// Logs:\n// (at 1 second): 1st subscribe: 1\n// (at 2 seconds): 1st subscribe: 2\n// (at 2 seconds): 2nd subscribe: 2\n// (at 3 seconds): 1st subscribe: 3\n// (at 3 seconds): 1st <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished\n// (at 3 seconds): 2nd subscribe: 3\n// (at 3 seconds): 2nd <a href=\"api/animations/sequence\" class=\"code-anchor\">sequence</a> finished\n\n\n</code-example>\n<div class=\"alert is-helpful\">\n Multicasting observables take a bit more setup, but they can be useful for certain applications. Later we will look at tools that simplify the process of multicasting, allowing you to take any observable and make it multicasting.\n</div>\n<h2 id=\"error-handling\">Error handling<a title=\"Link to this heading\" class=\"header-link\" aria-hidden=\"true\" href=\"guide/observables#error-handling\"><i class=\"material-icons\">link</i></a></h2>\n<p>Because observables produce values asynchronously, try/catch will not effectively catch errors. Instead, you handle errors by specifying an <code>error</code> callback on the observer. Producing an error also causes the observable to clean up subscriptions and stop producing values. An observable can either produce values (calling the <code>next</code> callback), or it can complete, calling either the <code>complete</code> or <code>error</code> callback.</p>\n<code-example>\nmyObservable.subscribe({\n next(num) { console.log('Next num: ' + num)},\n error(err) { console.log('Received an error: ' + err)}\n});\n</code-example>\n<p>Error handling (and specifically recovering from an error) is covered in more detail in a later section.</p>\n\n \n</div>\n\n<!-- links to this doc:\n - api/service-worker/SwRegistrationOptions\n - guide/architecture-next-steps\n - guide/glossary\n - guide/http\n-->\n<!-- links from this doc:\n - api/animations/sequence\n - guide/observables#basic-usage-and-terms\n - guide/observables#creating-observables\n - guide/observables#defining-observers\n - guide/observables#error-handling\n - guide/observables#multicasting\n - guide/observables#subscribing\n - guide/observables#using-observables-to-pass-values\n - https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern\n - https://github.com/angular/angular/edit/master/aio/content/guide/observables.md?message=docs%3A%20describe%20your%20change...\n-->"
|
||
} |