Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Commit 7c290d5

Browse files
committed
docs(add observables to TOH http)
e2e prose tweaks d d d section lint jade space update remove r test
1 parent 3c42db3 commit 7c290d5

File tree

11 files changed

+233
-3
lines changed

11 files changed

+233
-3
lines changed

public/docs/_examples/toh-6/e2e-spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,31 @@ describe('TOH Http Chapter', function () {
2323

2424
addButton: element.all(by.buttonText('Add New Hero')).get(0),
2525

26-
heroDetail: element(by.css('my-app my-hero-detail'))
26+
heroDetail: element(by.css('my-app my-hero-detail')),
27+
28+
searchBox: element(by.css('#search-box')),
29+
searchResults: element.all(by.css('.search-result'))
2730
};
2831
}
2932

33+
it('should search for hero and navigate to details view', function() {
34+
let page = getPageStruct();
35+
36+
return sendKeys(page.searchBox, 'Magneta').then(function () {
37+
expect(page.searchResults.count()).toBe(1);
38+
let hero = page.searchResults.get(0);
39+
return hero.click();
40+
})
41+
.then(function() {
42+
browser.waitForAngular();
43+
let inputEle = page.heroDetail.element(by.css('input'));
44+
return inputEle.getAttribute('value');
45+
})
46+
.then(function(value) {
47+
expect(value).toBe('Magneta');
48+
});
49+
});
50+
3051
it('should be able to add a hero from the "Heroes" view', function(){
3152
let page = getPageStruct();
3253
let heroCount: webdriver.promise.Promise<number>;

public/docs/_examples/toh-6/ts/app/app.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { Component } from '@angular/core';
44
import { ROUTER_DIRECTIVES } from '@angular/router';
55

66
import { HeroService } from './hero.service';
7+
// #docregion rxjs-operators
8+
import './rxjs-operators';
9+
// #enddocregion rxjs-operators
710

811
@Component({
912
selector: 'my-app',

public/docs/_examples/toh-6/ts/app/dashboard.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ <h4>{{hero.name}}</h4>
99
</div>
1010
</div>
1111
</div>
12+
<hero-search></hero-search>
13+

public/docs/_examples/toh-6/ts/app/dashboard.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { Router } from '@angular/router';
55

66
import { Hero } from './hero';
77
import { HeroService } from './hero.service';
8+
import { HeroSearchComponent } from './hero-search.component';
89

910
@Component({
1011
selector: 'my-dashboard',
1112
templateUrl: 'app/dashboard.component.html',
12-
styleUrls: ['app/dashboard.component.css']
13+
styleUrls: ['app/dashboard.component.css'],
14+
directives: [HeroSearchComponent]
1315
})
1416
export class DashboardComponent implements OnInit {
1517

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!-- #docregion -->
2+
<div id="search-component">
3+
<h4>Hero Search</h4>
4+
<input id="search-box" [ngFormControl]="search" />
5+
<div *ngIf="showResult">
6+
<div class="search-result" (click)="gotoDetail(hero)" *ngFor="let hero of heroes">
7+
{{hero.name}}
8+
</div>
9+
</div>
10+
</div>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// #docregion
2+
import { Component, OnInit } from '@angular/core';
3+
import { Control } from '@angular/common';
4+
import { Router } from '@angular/router';
5+
6+
import { HeroSearchService } from './hero-search.service';
7+
import { Hero } from './hero';
8+
9+
@Component({
10+
selector: 'hero-search',
11+
templateUrl: 'app/hero-search.component.html',
12+
providers: [HeroSearchService]
13+
})
14+
export class HeroSearchComponent implements OnInit {
15+
search = new Control('');
16+
heroes: Hero[] = [];
17+
showResult = true;
18+
19+
constructor(private _heroSearchService: HeroSearchService, private _router: Router) {}
20+
21+
gotoDetail(hero: Hero) {
22+
let link = ['/detail', hero.id];
23+
this._router.navigate(link);
24+
}
25+
26+
// #docregion search
27+
ngOnInit() {
28+
this.search.valueChanges
29+
.debounceTime(300)
30+
.distinctUntilChanged()
31+
.do(value => this.showResult = value.length > 0)
32+
.filter(value => value.length > 0)
33+
.switchMap(term => this._heroSearchService.search(term))
34+
.subscribe(result => this.heroes = result, error => console.log(error));
35+
}
36+
// #enddocregion search
37+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// #docregion
2+
import { Injectable } from '@angular/core';
3+
import { Http, Response } from '@angular/http';
4+
5+
@Injectable()
6+
export class HeroSearchService {
7+
8+
constructor(private _http: Http) {}
9+
10+
// #docregion observable-search
11+
search(term: string) {
12+
return this._http
13+
.get(`app/heroes/?name=${term}+`)
14+
.map((r: Response) => r.json().data);
15+
}
16+
// #enddocregion observable-search
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// #docregion
2+
import 'rxjs/add/operator/map';
3+
import 'rxjs/add/operator/switchMap';
4+
import 'rxjs/add/operator/filter';
5+
import 'rxjs/add/operator/do';
6+
import 'rxjs/add/operator/distinctUntilChanged';
7+
import 'rxjs/add/operator/debounceTime';

public/docs/_examples/toh-6/ts/sample.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,20 @@ button.delete-button{
55
background-color: gray !important;
66
color:white;
77
}
8+
9+
.search-result{
10+
border-bottom: 1px solid gray;
11+
border-left: 1px solid gray;
12+
border-right: 1px solid gray;
13+
width:195px;
14+
height: 20px;
15+
padding: 5px;
16+
background-color: white;
17+
cursor: pointer;
18+
}
19+
20+
#search-box{
21+
width: 200px;
22+
height: 20px;
23+
}
24+

public/docs/ts/latest/tutorial/toh-pt6.jade

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,103 @@ block heroes-comp-add
351351
But the component is still responsible for updating the display.
352352
So the *delete* method removes the deleted hero from the list.
353353

354+
:marked
355+
## Observables
356+
357+
In this section we discuss `Observables` as an alternative to promises when processing http calls.
358+
359+
`Http` calls return RxJS observables by default, but so far we've been hiding that by converting the observable to a promise by calling `toPromise`.
360+
361+
Observables and promises are both great for processing http calls, but as we will see, the observables api is much richer.
362+
363+
`RxJs` offers a wide variety of operators that we can use to manage event flows.
364+
365+
In this section we will discuss how to use some of these operators to build a type-to-search filter where we can search for heroes by name.
366+
367+
We start by creating `HeroSearchService`, a simple service for sending search queries to our api.
368+
369+
+makeExample('toh-6/ts/app/hero-search.service.ts', null, 'app/hero-search.service.ts')(format=".")
370+
371+
:marked
372+
The http call in `HeroSearchService` is not that different from our previous http calls, but we no longer call `toPromise`. This means we will return an observable instead of a promise.
373+
374+
Now, let's implement our search component `HeroSearchComponent`.
375+
376+
+makeTabs(
377+
`toh-6/ts/app/hero-search.component.ts,
378+
toh-6/ts/app/hero-search.component.html`,
379+
null,
380+
`hero-search.component.ts,
381+
hero-search.component.html`
382+
)
383+
384+
:marked
385+
The `HeroSearchComponent` UI is simple - just a textbox and a list of matching search results.
386+
387+
We have bound the `search` property to the textbox as an `ngFormControl` in order to track changes.
388+
389+
Observables are great for managing event streams. In our example we will actually be dealing with two different types of event streams:
390+
391+
1) Key events from typing into the search textbox
392+
393+
2) Results from http calls based on values entered in the search textbox
394+
395+
Although we are dealing with two different streams, we can use `RxJs` operators to combine them into a single stream.
396+
397+
Converging on a single stream is the end game, but let's start by going through the different parts of the observable chain.
398+
399+
+makeExample('toh-6/ts/app/hero-search.component.ts', 'search', 'app/hero-search.component.ts')(format=".")
400+
401+
:marked
402+
### valueChanges
403+
`ValueChanges` is an observable that represents text changes triggered by typing into the search textbox.
404+
405+
### debounceTime(300)
406+
By specifying a debounce time of 300 ms we are telling `RxJs` that we only want to be notified of key events after intervals of 300 ms. This is a performance measure meant to prevent us from hitting the api too often with partial search queries.
407+
408+
### distinctUntilChanged
409+
`distinctUntilChanged` tells `RxJs` to only react if the value of the textbox actually changed.
410+
411+
### do
412+
`do` is an operator for triggering side effects outside the observable chain. In our case we are using it to set a flag to hide the search results if the search textbox is empty.
413+
414+
### filter
415+
By specifying a `filter` we are telling `RxJs` to only process search values that meet our filter condition.
416+
417+
### switchMap
418+
`switchMap` is where observables from key events and observables from http calls converge on a single observable.
419+
420+
Every qualifying key event will trigger an http call, so we may get into a situation where we have multiple http calls in flight.
421+
422+
Normally this could lead to results being returned out of order, but `switchMap` has built in support for ensuring that only the most recent http call will be processed. Results from prior calls will be discarded.
423+
424+
### subscribe
425+
`subscribe` is similar to `then` in the promise world.
426+
427+
The first callback is the success callback where we grab the result of the search and assign it to our `heroes` array.
428+
429+
The second callback is the error callback. This callback will execute if there is an error - either from the http call or in our processing of key events.
430+
431+
In our current implementation we are just logging the error to the console, but a real life application should do better.
432+
433+
### Import operators
434+
These operators we are not included by default, so we have to load each one using `import` statements.
435+
436+
We have combined all operator imports in a single file.
437+
438+
+makeExample('toh-6/ts/app/rxjs-operators.ts', null, 'app/rxjs-operators.ts')(format=".")
439+
440+
:marked
441+
We can then load all the operators in a single operation by importing `rxjs-operators` in `AppComponent`
442+
443+
+makeExample('toh-6/ts/app/app.component.ts', 'rxjs-operators', 'app/app/app.component.ts')(format=".")
444+
445+
:marked
446+
Here is the final search component.
447+
448+
figure.image-display
449+
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
450+
354451
block review
355452
:marked
356453
### Let's see it
@@ -381,6 +478,10 @@ block filetree
381478
.file hero-detail.component.css
382479
.file hero-detail.component.html
383480
.file hero-detail.component.ts
481+
.file hero-search.component.html
482+
.file hero-search.component.ts
483+
.file hero-search.service.ts
484+
.file rxjs-operators.ts
384485
.file hero.service.ts
385486
.file heroes.component.css
386487
.file heroes.component.html
@@ -407,7 +508,8 @@ block filetree
407508
- We extended HeroService to support post, put and delete calls.
408509
- We updated our components to allow adding, editing and deleting of heroes.
409510
- We configured an in-memory web API.
410-
511+
- We learned how to use Observables.
512+
411513
Below is a summary of the files we changed and added.
412514

413515
block file-summary
@@ -430,3 +532,15 @@ block file-summary
430532
in-memory-data.service.ts,
431533
sample.css`
432534
)
535+
536+
+makeTabs(
537+
`toh-6/ts/app/hero-search.service.ts,
538+
toh-6/ts/app/hero-search.component.ts,
539+
toh-6/ts/app/hero-search.component.html,
540+
toh-6/ts/app/rxjs-operators.ts`,
541+
null,
542+
`hero-search.service.ts,
543+
hero-search.component.ts,
544+
hero-search.service.html,
545+
rxjs-operators.ts`
546+
)

0 commit comments

Comments
 (0)