Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cbc74b5

Browse files
committedJun 7, 2016
docs(add observables to TOH http)
e2e prose tweaks d d d
1 parent 21d74d5 commit cbc74b5

File tree

11 files changed

+239
-10
lines changed

11 files changed

+239
-10
lines changed
 

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

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

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

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

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

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
// #docregion
33
import { Component } from '@angular/core';
44
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated';
5-
5+
// #docregion rxjs-operators
6+
import './rxjs-operators';
7+
// #enddocregion rxjs-operators
68
import { DashboardComponent } from './dashboard.component';
79
import { HeroesComponent } from './heroes.component';
810
import { HeroDetailComponent } from './hero-detail.component';
11+
import { HeroSearchComponent } from './hero-search.component';
912
import { HeroService } from './hero.service';
1013

1114
@Component({
@@ -20,7 +23,7 @@ import { HeroService } from './hero.service';
2023
<router-outlet></router-outlet>
2124
`,
2225
styleUrls: ['app/app.component.css'],
23-
directives: [ROUTER_DIRECTIVES],
26+
directives: [ROUTER_DIRECTIVES, HeroSearchComponent],
2427
providers: [
2528
ROUTER_PROVIDERS,
2629
HeroService,

‎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: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
// #docplaster
22
// #docregion
33
import { Component, OnInit } from '@angular/core';
4-
import { Router } from '@angular/router-deprecated';
4+
import { Router } from '@angular/router-deprecated';
55

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

911
@Component({
1012
selector: 'my-dashboard',
1113
templateUrl: 'app/dashboard.component.html',
12-
styleUrls: ['app/dashboard.component.css']
14+
styleUrls: ['app/dashboard.component.css'],
15+
directives: [HeroSearchComponent]
1316
})
1417
export class DashboardComponent implements OnInit {
1518

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-deprecated';
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 = ['HeroDetail', { id: 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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,19 @@ button.delete-button{
44
color:white;
55
}
66

7+
.search-result{
8+
border-bottom: 1px solid gray;
9+
border-left: 1px solid gray;
10+
border-right: 1px solid gray;
11+
width:195px;
12+
height: 20px;
13+
padding: 5px;
14+
background-color: white;
15+
cursor: pointer;
16+
}
717

18+
#search-box{
19+
width: 200px;
20+
height: 20px;
21+
}
822

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

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ code-example(format="." language="bash").
110110

111111
The Angular `http.get` returns an RxJS `Observable`.
112112
*Observables* are a powerful way to manage asynchronous data flows.
113-
We'll learn about `Observables` *later*.
114-
113+
We'll learn about `Observables` in the next chapter.
114+
115115
For *now* we get back on familiar ground by immediately converting that `Observable` to a `Promise` using the `toPromise` operator.
116116
+makeExample('toh-6/ts/app/hero.service.ts', 'to-promise')(format=".")
117117
:marked
@@ -305,6 +305,104 @@ figure.image-display
305305
figure.image-display
306306
img(src='/resources/images/devguide/toh/toh-http.anim.gif' alt="Heroes List Editting w/ HTTP")
307307

308+
.l-main-section
309+
:marked
310+
## Observables
311+
312+
In this section we discuss `Observables` as an alternative to promises when processing http calls.
313+
314+
`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`.
315+
316+
Observables and promises are both great for processing http calls, but as we will see, the observables api is much richer.
317+
318+
`RxJs` offers a wide variety of operators that we can use to manage event flows.
319+
320+
In this chapter 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.
321+
322+
We start by creating `HeroSearchService`, a simple service for sending search queries to our api.
323+
324+
+makeExample('toh-6/ts/app/hero-search.service.ts', null, 'app/hero-search.service.ts')(format=".")
325+
326+
:marked
327+
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.
328+
329+
Now, let's implement our search component `HeroSearchComponent`.
330+
331+
+makeTabs(
332+
`toh-6/ts/app/hero-search.component.ts,
333+
toh-6/ts/app/hero-search.component.html`,
334+
null,
335+
`hero-search.component.ts,
336+
hero-search.component.html`
337+
)
338+
339+
:marked
340+
The `HeroSearchComponent` UI is simple - just a textbox and a list of matching search results.
341+
342+
We have bound the `search` property to the textbox as an `ngFormControl` in order to track changes.
343+
344+
Observables are great for managing event streams. In our example we will actually be dealing with two different types of event streams:
345+
346+
1) Key events from typing into the search textbox
347+
348+
2) Results from http calls based on values entered in the search textbox
349+
350+
Although we are dealing with two different streams, we can use `RxJs` operators to combine them into a single stream.
351+
352+
Converging on a single stream is the end game, but let's start by going through the different parts of the observable chain.
353+
354+
+makeExample('toh-6/ts/app/hero-search.component.ts', 'search', 'app/hero-search.component.ts')(format=".")
355+
356+
:marked
357+
### valueChanges
358+
`ValueChanges` is an observable that represents text changes triggered by typing into the search textbox.
359+
360+
### debounceTime(300)
361+
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.
362+
363+
### distinctUntilChanged
364+
`distinctUntilChanged` tells `RxJs` to only react if the value of the textbox actually changed.
365+
366+
### do
367+
`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.
368+
369+
### filter
370+
By specifying a `filter` we are telling `RxJs` to only process search values that meet our filter condition.
371+
372+
### switchMap
373+
`switchMap` is where observables from key events and observables from http calls converge on a single observable.
374+
375+
Every qualifying key event will trigger an http call, so we may get into a situation where we have multiple http calls in flight.
376+
377+
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.
378+
379+
### subscribe
380+
`subscribe` is similar to `then` in the promise world.
381+
382+
The first callback is the success callback where we grab the result of the search and assign it to our `heroes` array.
383+
384+
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.
385+
386+
In our current implementation we are just logging the error to the console, but a real life application should do better.
387+
388+
### Import operators
389+
These operators we are not included by default, so we have to load each one using `import` statements.
390+
391+
We have combined all operator imports in a single file.
392+
393+
+makeExample('toh-6/ts/app/rxjs-operators.ts', null, 'app/rxjs-operators.ts')(format=".")
394+
395+
:marked
396+
We can then load all the operators in a single operation by importing `rxjs-operators` in `AppComponent`
397+
398+
+makeExample('toh-6/ts/app/app.component.ts', 'rxjs-operators', 'app/app/app.component.ts')(format=".")
399+
400+
:marked
401+
Here is the final search component.
402+
403+
figure.image-display
404+
img(src='/resources/images/devguide/toh/toh-hero-search.png' alt="Hero Search Component")
405+
308406
:marked
309407
### Review the App Structure
310408
Let’s verify that we have the following structure after all of our good refactoring in this chapter:
@@ -323,11 +421,15 @@ figure.image-display
323421
.file hero-detail.component.css
324422
.file hero-detail.component.html
325423
.file hero-detail.component.ts
424+
.file hero-search.component.html
425+
.file hero-search.component.ts
426+
.file hero-search.service.ts
326427
.file hero.service.ts
327428
.file heroes.component.css
328429
.file heroes.component.html
329430
.file heroes.component.ts
330431
.file main.ts
432+
.file rxjs-operators.ts
331433
.file hero-data.service.ts
332434
.file node_modules ...
333435
.file typings ...
@@ -349,7 +451,8 @@ figure.image-display
349451
- We extended HeroService to support post, put and delete calls.
350452
- We updated our components to allow adding, editing and deleting of heroes.
351453
- We configured an in-memory web api.
352-
454+
- We learned how to use Observables.
455+
353456
Below is a summary of the files we changed.
354457

355458
+makeTabs(
@@ -366,4 +469,16 @@ figure.image-display
366469
hero-detail.comp...ts,
367470
hero-detail.comp...html,
368471
hero.service.ts`
369-
)
472+
)
473+
474+
+makeTabs(
475+
`toh-6/ts/app/hero-search.service.ts,
476+
toh-6/ts/app/hero-search.component.ts,
477+
toh-6/ts/app/hero-search.component.html,
478+
toh-6/ts/app/rxjs-operators.ts`,
479+
null,
480+
`hero-search.service.ts,
481+
hero-search.component.ts,
482+
hero-search.service.html,
483+
rxjs-operators.ts`
484+
)
Loading

0 commit comments

Comments
 (0)
This repository has been archived.