docs(toh-6/dart): quickfix to "BAD FILENAME" errors (#1929)
Exclude the new Observables section entirely for now.
This commit is contained in:
parent
753452650c
commit
b4c92d9c9c
@ -89,6 +89,9 @@ block heroes-comp-add
|
|||||||
block review
|
block review
|
||||||
//- Not showing animated gif due to differences between TS and Dart implementations.
|
//- Not showing animated gif due to differences between TS and Dart implementations.
|
||||||
|
|
||||||
|
block observables-section
|
||||||
|
//- TBC
|
||||||
|
|
||||||
block filetree
|
block filetree
|
||||||
.filetree
|
.filetree
|
||||||
.file angular2_tour_of_heroes
|
.file angular2_tour_of_heroes
|
||||||
|
@ -359,147 +359,148 @@ block review
|
|||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/toh-http.anim.gif' alt="Heroes List Editting w/ HTTP")
|
img(src='/resources/images/devguide/toh/toh-http.anim.gif' alt="Heroes List Editting w/ HTTP")
|
||||||
|
|
||||||
:marked
|
block observables-section
|
||||||
## Observables
|
|
||||||
|
|
||||||
Each `Http` method returns an `Observable` of HTTP `Response` objects.
|
|
||||||
|
|
||||||
Our `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
|
|
||||||
In this section we learn to return the `Observable` directly and discuss when and why that might be
|
|
||||||
a good thing to do.
|
|
||||||
|
|
||||||
### Background
|
|
||||||
An *observable* is a stream of events that we can process with array-like operators.
|
|
||||||
|
|
||||||
Angular core has basic support for observables. We developers augment that support with
|
|
||||||
operators and extensions from the [RxJS Observables](http://reactivex.io/rxjs/) library.
|
|
||||||
We'll see how shortly.
|
|
||||||
|
|
||||||
Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`.
|
|
||||||
That operator converted the `Observable` into a `Promise` and we passed that promise back to the caller.
|
|
||||||
|
|
||||||
Converting to a promise is often a good choice. We typically ask `http` to fetch a single chunk of data.
|
|
||||||
When we receive the data, we're done.
|
|
||||||
A single result in the form of a promise is easy for the calling component to consume
|
|
||||||
and it helps that promises are widely understood by JavaScript programmers.
|
|
||||||
|
|
||||||
But requests aren't always "one and done". We may start one request,
|
|
||||||
then cancel it, and make a different request ... before the server has responded to the first request.
|
|
||||||
Such a _request-cancel-new-request_ sequence is difficult to implement with *promises*.
|
|
||||||
It's easy with *observables* as we'll see.
|
|
||||||
|
|
||||||
### Search-by-name
|
|
||||||
We're going to add a *hero search* feature to the Tour of Heroes.
|
|
||||||
As the user types a name into a search box, we'll make repeated http requests for heroes filtered by that name.
|
|
||||||
|
|
||||||
We start by creating `HeroSearchService` that sends search queries to our server's web api.
|
|
||||||
|
|
||||||
+makeExample('toh-6/ts/app/hero-search.service.ts', null, 'app/hero-search.service.ts')(format=".")
|
|
||||||
|
|
||||||
:marked
|
|
||||||
The `http.get` call in `HeroSearchService` is similar to the `http.get` call in the `HeroService`.
|
|
||||||
The notable difference: we no longer call `toPromise`.
|
|
||||||
We simply return the *observable* instead.
|
|
||||||
|
|
||||||
### HeroSearchComponent
|
|
||||||
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
|
|
||||||
|
|
||||||
The component template is simple - just a textbox and a list of matching search results.
|
|
||||||
+makeExample('toh-6/ts/app/hero-search.component.html', null,'hero-search.component.html')
|
|
||||||
:marked
|
|
||||||
As the user types in the search box, a *keyup* event binding calls the component's `search` with the new search box value.
|
|
||||||
|
|
||||||
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
|
|
||||||
|
|
||||||
But, as we'll soon see, the `heroes` property returns an `Observable` of heroes, not an array of heroes.
|
|
||||||
The `*ngFor` can't do anything with an observable until we flow it through the `AsyncPipe` (`heroes | async`).
|
|
||||||
The `AsyncPipe` subscribes to the observable and produces the array of heroes to `*ngFor`.
|
|
||||||
|
|
||||||
Time to create the `HeroSearchComponent` class and metadata.
|
|
||||||
+makeExample('toh-6/ts/app/hero-search.component.ts', null,'hero-search.component.ts')
|
|
||||||
:marked
|
|
||||||
Focus on the `searchSubject`.
|
|
||||||
+makeExample('toh-6/ts/app/hero-search.component.ts', 'searchSubject')(format=".")
|
|
||||||
:marked
|
|
||||||
A `Subject` is a producer of an _observable_ event stream.
|
|
||||||
This `searchSubject` produces an `Observable` of strings, the filter criteria for the name search.
|
|
||||||
|
|
||||||
Each call to `search` puts a new string into this subject's _observable_ stream by calling `next`.
|
|
||||||
|
|
||||||
A `Subject` is also an `Observable`.
|
|
||||||
We're going to access that `Observable` and turn the stream
|
|
||||||
of strings into a stream of `Hero[]` arrays, the `heroes` property.
|
|
||||||
|
|
||||||
+makeExample('toh-6/ts/app/hero-search.component.ts', 'search')(format=".")
|
|
||||||
:marked
|
|
||||||
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of http requests.
|
|
||||||
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
|
|
||||||
|
|
||||||
Fortunately we can chain `Observable` operators to the string `Observable` that reduce the request flow.
|
|
||||||
We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
|
|
||||||
|
|
||||||
* The `asObservable` operator casts the `Subject` as an `Observable` of filter strings.
|
|
||||||
|
|
||||||
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
|
|
||||||
before passing along the latest string. We'll never make requests more frequently than 300ms.
|
|
||||||
|
|
||||||
* `distinctUntilChanged` ensures that we only send a request if the filter text changed.
|
|
||||||
There's no point in repeating a request for the same search term.
|
|
||||||
|
|
||||||
* `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet.
|
|
||||||
It cancels and discards previous search observables, returning only the latest search service observable.
|
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
:marked
|
||||||
The [switchMap operator](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)
|
## Observables
|
||||||
(formerly known as "flatMapLatest") is very clever.
|
|
||||||
|
|
||||||
Every qualifying key event can trigger an http call.
|
Each `Http` method returns an `Observable` of HTTP `Response` objects.
|
||||||
Even with a 300ms pause between requests, we could have multiple http requests in flight
|
|
||||||
and they may not return in the order sent.
|
|
||||||
|
|
||||||
`switchMap` preserves the original request order while returning
|
Our `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
|
||||||
only the observable from the most recent http call.
|
In this section we learn to return the `Observable` directly and discuss when and why that might be
|
||||||
Results from prior calls are canceled and discarded.
|
a good thing to do.
|
||||||
|
|
||||||
We also short-circuit the http call and return an observable containing an empty array
|
### Background
|
||||||
if the search text is empty.
|
An *observable* is a stream of events that we can process with array-like operators.
|
||||||
|
|
||||||
Note that _canceling_ the `HeroSearchService` observable won't actually abort a pending http request
|
Angular core has basic support for observables. We developers augment that support with
|
||||||
until the service supports that feature, a topic for another day.
|
operators and extensions from the [RxJS Observables](http://reactivex.io/rxjs/) library.
|
||||||
We are content for now to discard unwanted results.
|
We'll see how shortly.
|
||||||
:marked
|
|
||||||
* `catch` intercepts a failed observable.
|
|
||||||
Our simple example prints the error to the console; a real life application should do better.
|
|
||||||
Then it re-throws the failed observable so that downstream processes know it failed.
|
|
||||||
The `AsyncPipe` in the template is downstream. It sees the failure and ignores it.
|
|
||||||
|
|
||||||
### Import RxJS operators
|
Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`.
|
||||||
The RxJS operators are not available in Angular's base `Observable` implementation.
|
That operator converted the `Observable` into a `Promise` and we passed that promise back to the caller.
|
||||||
We have to extend `Observable` by *importing* them.
|
|
||||||
|
|
||||||
We could extend `Observable` with just the operators we need here by
|
Converting to a promise is often a good choice. We typically ask `http` to fetch a single chunk of data.
|
||||||
including the pertinent `import` statements at the top of this file.
|
When we receive the data, we're done.
|
||||||
|
A single result in the form of a promise is easy for the calling component to consume
|
||||||
|
and it helps that promises are widely understood by JavaScript programmers.
|
||||||
|
|
||||||
|
But requests aren't always "one and done". We may start one request,
|
||||||
|
then cancel it, and make a different request ... before the server has responded to the first request.
|
||||||
|
Such a _request-cancel-new-request_ sequence is difficult to implement with *promises*.
|
||||||
|
It's easy with *observables* as we'll see.
|
||||||
|
|
||||||
|
### Search-by-name
|
||||||
|
We're going to add a *hero search* feature to the Tour of Heroes.
|
||||||
|
As the user types a name into a search box, we'll make repeated http requests for heroes filtered by that name.
|
||||||
|
|
||||||
|
We start by creating `HeroSearchService` that sends search queries to our server's web api.
|
||||||
|
|
||||||
|
+makeExample('toh-6/ts/app/hero-search.service.ts', null, 'app/hero-search.service.ts')(format=".")
|
||||||
|
|
||||||
.l-sub-section
|
|
||||||
:marked
|
:marked
|
||||||
Many authorities say we should do just that.
|
The `http.get` call in `HeroSearchService` is similar to the `http.get` call in the `HeroService`.
|
||||||
:marked
|
The notable difference: we no longer call `toPromise`.
|
||||||
We take a different approach in this example.
|
We simply return the *observable* instead.
|
||||||
We combine all of the RxJS `Observable` extensions that _our entire app_ requires into a single RxJS imports file.
|
|
||||||
|
|
||||||
+makeExample('toh-6/ts/app/rxjs-extensions.ts', null, 'app/rxjs-extensions.ts')(format=".")
|
### HeroSearchComponent
|
||||||
:marked
|
Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
|
||||||
We load them all at once by importing `rxjs-extensions` in `AppComponent`.
|
|
||||||
|
|
||||||
+makeExample('toh-6/ts/app/app.component.ts', 'rxjs-extensions', 'app/app/app.component.ts')(format=".")
|
The component template is simple - just a textbox and a list of matching search results.
|
||||||
:marked
|
+makeExample('toh-6/ts/app/hero-search.component.html', null,'hero-search.component.html')
|
||||||
Finally, we add the `HeroSearchComponent` to the bottom of the `DashboardComponent`.
|
:marked
|
||||||
Run the app again, go to the *Dashboard*, and enter some text in the search box below the hero tiles.
|
As the user types in the search box, a *keyup* event binding calls the component's `search` with the new search box value.
|
||||||
At some point it might look like this.
|
|
||||||
|
|
||||||
figure.image-display
|
The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
|
||||||
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
|
|
||||||
|
But, as we'll soon see, the `heroes` property returns an `Observable` of heroes, not an array of heroes.
|
||||||
|
The `*ngFor` can't do anything with an observable until we flow it through the `AsyncPipe` (`heroes | async`).
|
||||||
|
The `AsyncPipe` subscribes to the observable and produces the array of heroes to `*ngFor`.
|
||||||
|
|
||||||
|
Time to create the `HeroSearchComponent` class and metadata.
|
||||||
|
+makeExample('toh-6/ts/app/hero-search.component.ts', null,'hero-search.component.ts')
|
||||||
|
:marked
|
||||||
|
Focus on the `searchSubject`.
|
||||||
|
+makeExample('toh-6/ts/app/hero-search.component.ts', 'searchSubject')(format=".")
|
||||||
|
:marked
|
||||||
|
A `Subject` is a producer of an _observable_ event stream.
|
||||||
|
This `searchSubject` produces an `Observable` of strings, the filter criteria for the name search.
|
||||||
|
|
||||||
|
Each call to `search` puts a new string into this subject's _observable_ stream by calling `next`.
|
||||||
|
|
||||||
|
A `Subject` is also an `Observable`.
|
||||||
|
We're going to access that `Observable` and turn the stream
|
||||||
|
of strings into a stream of `Hero[]` arrays, the `heroes` property.
|
||||||
|
|
||||||
|
+makeExample('toh-6/ts/app/hero-search.component.ts', 'search')(format=".")
|
||||||
|
:marked
|
||||||
|
If we passed every user keystroke directly to the `HeroSearchService`, we'd unleash a storm of http requests.
|
||||||
|
Bad idea. We don't want to tax our server resources and burn through our cellular network data plan.
|
||||||
|
|
||||||
|
Fortunately we can chain `Observable` operators to the string `Observable` that reduce the request flow.
|
||||||
|
We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
|
||||||
|
|
||||||
|
* The `asObservable` operator casts the `Subject` as an `Observable` of filter strings.
|
||||||
|
|
||||||
|
* `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
|
||||||
|
before passing along the latest string. We'll never make requests more frequently than 300ms.
|
||||||
|
|
||||||
|
* `distinctUntilChanged` ensures that we only send a request if the filter text changed.
|
||||||
|
There's no point in repeating a request for the same search term.
|
||||||
|
|
||||||
|
* `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet.
|
||||||
|
It cancels and discards previous search observables, returning only the latest search service observable.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
The [switchMap operator](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)
|
||||||
|
(formerly known as "flatMapLatest") is very clever.
|
||||||
|
|
||||||
|
Every qualifying key event can trigger an http call.
|
||||||
|
Even with a 300ms pause between requests, we could have multiple http requests in flight
|
||||||
|
and they may not return in the order sent.
|
||||||
|
|
||||||
|
`switchMap` preserves the original request order while returning
|
||||||
|
only the observable from the most recent http call.
|
||||||
|
Results from prior calls are canceled and discarded.
|
||||||
|
|
||||||
|
We also short-circuit the http call and return an observable containing an empty array
|
||||||
|
if the search text is empty.
|
||||||
|
|
||||||
|
Note that _canceling_ the `HeroSearchService` observable won't actually abort a pending http request
|
||||||
|
until the service supports that feature, a topic for another day.
|
||||||
|
We are content for now to discard unwanted results.
|
||||||
|
:marked
|
||||||
|
* `catch` intercepts a failed observable.
|
||||||
|
Our simple example prints the error to the console; a real life application should do better.
|
||||||
|
Then it re-throws the failed observable so that downstream processes know it failed.
|
||||||
|
The `AsyncPipe` in the template is downstream. It sees the failure and ignores it.
|
||||||
|
|
||||||
|
### Import RxJS operators
|
||||||
|
The RxJS operators are not available in Angular's base `Observable` implementation.
|
||||||
|
We have to extend `Observable` by *importing* them.
|
||||||
|
|
||||||
|
We could extend `Observable` with just the operators we need here by
|
||||||
|
including the pertinent `import` statements at the top of this file.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
Many authorities say we should do just that.
|
||||||
|
:marked
|
||||||
|
We take a different approach in this example.
|
||||||
|
We combine all of the RxJS `Observable` extensions that _our entire app_ requires into a single RxJS imports file.
|
||||||
|
|
||||||
|
+makeExample('toh-6/ts/app/rxjs-extensions.ts', null, 'app/rxjs-extensions.ts')(format=".")
|
||||||
|
:marked
|
||||||
|
We load them all at once by importing `rxjs-extensions` in `AppComponent`.
|
||||||
|
|
||||||
|
+makeExample('toh-6/ts/app/app.component.ts', 'rxjs-extensions', 'app/app/app.component.ts')(format=".")
|
||||||
|
:marked
|
||||||
|
Finally, we add the `HeroSearchComponent` to the bottom of the `DashboardComponent`.
|
||||||
|
Run the app again, go to the *Dashboard*, and enter some text in the search box below the hero tiles.
|
||||||
|
At some point it might look like this.
|
||||||
|
|
||||||
|
figure.image-display
|
||||||
|
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
@ -554,7 +555,7 @@ block filetree
|
|||||||
- We extended HeroService to support post, put and delete calls.
|
- We extended HeroService to support post, put and delete calls.
|
||||||
- We updated our components to allow adding, editing and deleting of heroes.
|
- We updated our components to allow adding, editing and deleting of heroes.
|
||||||
- We configured an in-memory web API.
|
- We configured an in-memory web API.
|
||||||
- We learned how to use Observables.
|
<li if-docs="ts"> We learned how to use Observables.</li>
|
||||||
|
|
||||||
Below is a summary of the files we changed and added.
|
Below is a summary of the files we changed and added.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user