@@ -131,9 +131,10 @@ block get-heroes-details
131
131
:marked
132
132
The Angular `http.get` returns an RxJS `Observable`.
133
133
*Observables* are a powerful way to manage asynchronous data flows.
134
- We'll learn about ` Observables` * later* .
134
+ We'll learn about [ Observables](#observables) later in this chapter .
135
135
136
- For *now* we get back on familiar ground by immediately converting that `Observable` to a `Promise` using the `toPromise` operator.
136
+ For *now* we get back on familiar ground by immediately by
137
+ converting that `Observable` to a `Promise` using the `toPromise` operator.
137
138
+ makeExample('toh-6/ts/app/hero.service.ts' , 'to-promise' )( format ="." )
138
139
:marked
139
140
Unfortunately, the Angular `Observable` doesn't have a `toPromise` operator ... not out of the box.
@@ -358,6 +359,146 @@ block review
358
359
figure.image-display
359
360
img( src ='/resources/images/devguide/toh/toh-http.anim.gif' alt ="Heroes List Editting w/ HTTP" )
360
361
362
+ :marked
363
+ ## Observables
364
+
365
+ Each `Http` method returns an `Observable` of HTTP `Response` objects.
366
+
367
+ Our `HeroService` converts that `Observable` into a `Promise` and returns the promise to the caller.
368
+ In this section we learn to return the `Observable` directly and discuss when and why that might be
369
+ a good thing to do.
370
+
371
+ ### Background
372
+ An *observable* is a stream of events that we can process with array-like operators.
373
+
374
+ Angular core has basic support for observables. We developers augment that support with
375
+ operators and extensions from the [RxJS Observables](http://reactivex.io/rxjs/) library.
376
+ We'll see how shortly.
377
+
378
+ Recall that our `HeroService` quickly chained the `toPromise` operator to the `Observable` result of `http.get`.
379
+ That operator converted the `Observable` into a `Promise` and we passed that promise back to the caller.
380
+
381
+ Converting to a promise is often a good choice. We typically ask `http` to fetch a single chunk of data.
382
+ When we receive the data, we're done.
383
+ A single result in the form of a promise is easy for the calling component to consume
384
+ and it helps that promises are widely understood by JavaScript programmers.
385
+
386
+ But requests aren't always "one and done". We may start one request,
387
+ then cancel it, and make a different request ... before the server has responded to the first request.
388
+ Such a _request-cancel-new-request_ sequence is difficult to implement with *promises*.
389
+ It's easy with *observables* as we'll see.
390
+
391
+ ### Search-by-name
392
+ We're going to add a *hero search* feature to the Tour of Heroes.
393
+ As the user types a name into a search box, we'll make repeated http requests for heroes filtered by that name.
394
+
395
+ We start by creating `HeroSearchService` that sends search queries to our server's web api.
396
+
397
+ + makeExample('toh-6/ts/app/hero-search.service.ts' , null , 'app/hero-search.service.ts' )( format ="." )
398
+
399
+ :marked
400
+ The `http.get` call in `HeroSearchService` is similar to the `http.get` call in the `HeroService`.
401
+ The notable difference: we no longer call `toPromise`.
402
+ We simply return the *observable* instead.
403
+
404
+ ### HeroSearchComponent
405
+ Let's create a new `HeroSearchComponent` that calls this new `HeroSearchService`.
406
+
407
+ The component template is simple - just a textbox and a list of matching search results.
408
+ + makeExample('toh-6/ts/app/hero-search.component.html' , null ,'hero-search.component.html' )
409
+ :marked
410
+ As the user types in the search box, a *keyup* event binding calls `search.next` with the new search box value.
411
+
412
+ The component's data bound `search` property returns a `Subject`.
413
+ A `Subject` is a producer of an _observable_ event stream.
414
+ Each call to `search.next` puts a new string into this subject's _observable_ stream.
415
+
416
+ The `*ngFor` repeats *hero* objects from the component's `heroes` property. No surprise there.
417
+
418
+ But `heroes` is an `Observable` of heroes, not an array of heroes.
419
+ The `*ngFor` can't do anything with that until we flow it through the `AsyncPipe` (`heroes | async`).
420
+ The `AsyncPipe` subscribes to the observable and produces the array of heroes to `*ngFor`.
421
+
422
+ Time to create the `HeroSearchComponent` class and metadata.
423
+ + makeExample('toh-6/ts/app/hero-search.component.ts' , null ,'hero-search.component.ts' )
424
+ :marked
425
+ Scroll down to where we create the `search` subject.
426
+ + makeExample('toh-6/ts/app/hero-search.component.ts' , 'subject' )
427
+ :marked
428
+ We're binding to that `search` subject in our template.
429
+ The user is sending it a stream of strings, the filter criteria for the name search.
430
+
431
+ A `Subject` is also an `Observable`.
432
+ We're going to access that `Observable` and append operators to it that turn the stream
433
+ of strings into a stream of `Hero[]` arrays.
434
+
435
+ Each user keystroke could result in a new http request returning a new Observable array of heroes.
436
+
437
+ This could be a very chatty, taxing our server resources and burning up our cellular network data plan.
438
+ Fortunately we can chain `Observable` operators to reduce the request flow
439
+ and still get timely results. Here's how:
440
+
441
+ + makeExample('toh-6/ts/app/hero-search.component.ts' , 'search' )( format ="." )
442
+ :marked
443
+ * The `asObservable` operator casts the `Subject` as an `Observable` of filter strings.
444
+
445
+ * `debounceTime(300)` waits until the flow of new string events pauses for 300 milliseconds
446
+ before passing along the latest string. We'll never make requests more frequently than 300ms.
447
+
448
+ * `distinctUntilChanged` ensures that we only send a request if the filter text changed.
449
+ There's no point in repeating a request for the same search term.
450
+
451
+ * `switchMap` calls our search service for each search term that makes it through the `debounce` and `distinctUntilChanged` gauntlet.
452
+ It discards previous search observables, returning only the latest search service observable.
453
+
454
+ .l-sub-section
455
+ :marked
456
+ The [switchMap operator](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/flatmaplatest.md)
457
+ (formerly known as "flatMapLatest") is very clever.
458
+
459
+ Every qualifying key event can trigger an http call.
460
+ Even with a 300ms pause between requests, we could have multiple http requests in flight
461
+ and they may not return in the order sent.
462
+
463
+ `switchMap` preserves the original request order while returning
464
+ only the observable from the most recent http call.
465
+ Results from prior calls will be discarded.
466
+
467
+ We also short-circuit the http call and return an observable containing an empty array
468
+ if the search text is empty.
469
+ :marked
470
+ * `catch` intercepts a failed observable.
471
+ Our simple example prints the error to the console; a real life application should do better.
472
+ Then it re-throws the failed observable so that downstream processes know it failed.
473
+ The `AsyncPipe` in the template is downstream. It sees the failure and ignores it.
474
+
475
+ ### Import RxJS operators
476
+ The RxJS operators are not available in Angular's base `Observable` implementation.
477
+ We have to extend `Observable` by *importing* them.
478
+
479
+ We could extend `Observable` with just the operators we need here by
480
+ including the pertinent `import` statements at the top of this file.
481
+
482
+ .l-sub-section
483
+ :marked
484
+ Many authorities say we should do just that.
485
+ :marked
486
+ We take a different approach in this example.
487
+ We combine all of the RxJS `Observable` extensions that _our entire app_ requires into a single RxJS imports file.
488
+
489
+ + makeExample('toh-6/ts/app/rxjs-extensions.ts' , null , 'app/rxjs-extensions.ts' )( format ="." )
490
+ :marked
491
+ We load them all at once by importing `rxjs-extensions` in `AppComponent`.
492
+
493
+ + makeExample('toh-6/ts/app/app.component.ts' , 'rxjs-extensions' , 'app/app/app.component.ts' )( format ="." )
494
+ :marked
495
+ Finally, we add the `HeroSearchComponent` to the bottom of the `DashboardComponent`.
496
+ Run the app again, go to the *Dashboard*, and enter some text in the search box below the hero tiles.
497
+ At some point it might look like this.
498
+
499
+ figure.image-display
500
+ img( src ='/resources/images/devguide/toh/toh-hero-search.png' alt ="Hero Search Component" )
501
+
361
502
.l-main-section
362
503
:marked
363
504
## Application structure and code
@@ -381,6 +522,10 @@ block filetree
381
522
.file hero-detail.component.css
382
523
.file hero-detail.component.html
383
524
.file hero-detail.component.ts
525
+ .file hero-search.component.html
526
+ .file hero-search.component.ts
527
+ .file hero-search.service.ts
528
+ .file rxjs-operators.ts
384
529
.file hero.service.ts
385
530
.file heroes.component.css
386
531
.file heroes.component.html
@@ -407,7 +552,8 @@ block filetree
407
552
- We extended HeroService to support post, put and delete calls.
408
553
- We updated our components to allow adding, editing and deleting of heroes.
409
554
- We configured an in-memory web API.
410
-
555
+ - We learned how to use Observables.
556
+
411
557
Below is a summary of the files we changed and added.
412
558
413
559
block file-summary
@@ -430,3 +576,15 @@ block file-summary
430
576
in-memory-data.service.ts,
431
577
sample.css`
432
578
)
579
+
580
+ + makeTabs(
581
+ ` toh-6/ts/app/hero-search.service.ts,
582
+ toh-6/ts/app/hero-search.component.ts,
583
+ toh-6/ts/app/hero-search.component.html,
584
+ toh-6/ts/app/rxjs-operators.ts` ,
585
+ null ,
586
+ ` hero-search.service.ts,
587
+ hero-search.component.ts,
588
+ hero-search.service.html,
589
+ rxjs-operators.ts`
590
+ )
0 commit comments