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

Commit 17618d1

Browse files
docs(rxjs): Added developer guide on Observables
1 parent 35bbeb2 commit 17618d1

24 files changed

+788
-0
lines changed
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'; // necessary for es6 output in node
2+
3+
import { browser
4+
/*, element, by, ElementFinder*/
5+
} from 'protractor';
6+
7+
describe('RxJS', function () {
8+
9+
beforeAll(function () {
10+
browser.get('');
11+
});
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<h2>ADD HERO</h2>
2+
<form [formGroup]="form" (ngSubmit)="save(form.value)">
3+
<p>
4+
*Name: <input type="text" formControlName="name"><br>
5+
<span *ngIf="showErrors && form.get('name').errors" class="error">Name is required</span>
6+
</p>
7+
<p>
8+
Description: <input type="text" formControlName="description">
9+
</p>
10+
<p>
11+
<button type="submit" [disabled]="showErrors">Save</button>
12+
</p>
13+
</form>
14+
15+
<span *ngIf="success">The hero has been added</span>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/observable/of';
4+
import 'rxjs/add/observable/fromEvent';
5+
import 'rxjs/add/observable/merge';
6+
import 'rxjs/add/observable/of';
7+
import 'rxjs/add/operator/debounceTime';
8+
import 'rxjs/add/operator/do';
9+
import { Component, OnInit, OnDestroy, AfterViewInit, ViewChildren, ElementRef } from '@angular/core';
10+
import { FormBuilder, FormGroup, FormControlName, Validators } from '@angular/forms';
11+
import { Observable } from 'rxjs/Observable';
12+
import { Subscription } from 'rxjs/Subscription';
13+
14+
import { HeroService } from './hero.service';
15+
16+
@Component({
17+
moduleId: module.id,
18+
templateUrl: 'add-hero.component.html',
19+
styles: [ '.error { color: red }' ]
20+
})
21+
export class AddHeroComponent implements OnInit, OnDestroy, AfterViewInit {
22+
@ViewChildren(FormControlName, { read: ElementRef }) formControls: ElementRef[];
23+
24+
form: FormGroup;
25+
sub: Subscription;
26+
showErrors: boolean = false;
27+
submitted: boolean = false;
28+
success: boolean;
29+
30+
constructor(
31+
private formBuilder: FormBuilder,
32+
private heroService: HeroService
33+
) {}
34+
35+
ngOnInit() {
36+
this.form = this.formBuilder.group({
37+
name: ['', [Validators.required]],
38+
description: ['']
39+
});
40+
}
41+
42+
ngAfterViewInit() {
43+
const controlBlurs: Observable<Event>[] = this.formControls.map(field => Observable.fromEvent(field.nativeElement, 'blur'));
44+
45+
this.sub = Observable.merge(
46+
this.form.valueChanges,
47+
...controlBlurs
48+
)
49+
.debounceTime(300)
50+
.subscribe(() => this.checkErrors());
51+
}
52+
53+
checkErrors() {
54+
if (!this.form.valid) {
55+
this.showErrors = true;
56+
}
57+
}
58+
59+
save(model: any) {
60+
this.success = false;
61+
this.submitted = true;
62+
63+
this.heroService.addHero(model)
64+
.do(() => {
65+
this.success = true;
66+
this.submitted = false;
67+
})
68+
.subscribe();
69+
}
70+
71+
ngOnDestroy() {
72+
this.sub.unsubscribe();
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Injectable } from '@angular/core';
2+
import { Response } from '@angular/http';
3+
import { Observable } from 'rxjs/Observable';
4+
5+
export interface ApiError {
6+
message: string;
7+
}
8+
9+
@Injectable()
10+
export class ApiErrorHandlerService {
11+
handle(resp: Response): Observable<Error> {
12+
return Observable.of(resp)
13+
.switchMap(response => {
14+
15+
let error: ApiError;
16+
17+
try {
18+
error = response.json().error;
19+
} catch (e) {
20+
if (response.status === 404) {
21+
error = {
22+
message: 'The requested resource was not found'
23+
};
24+
} else {
25+
error = {
26+
message: 'An unknown error has occurred'
27+
};
28+
}
29+
}
30+
31+
return Observable.throw(error);
32+
});
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// #docplaster
2+
// #docregion
3+
import { NgModule } from '@angular/core';
4+
import { RouterModule, Routes } from '@angular/router';
5+
import { AddHeroComponent } from './add-hero.component';
6+
import { HeroDetailComponent } from './hero-detail.component';
7+
import { HeroSearchComponent } from './hero-search.component';
8+
import { HeroListComponent } from './hero-list.component';
9+
10+
const appRoutes: Routes = [
11+
{ path: 'heroes/add', component: AddHeroComponent },
12+
{ path: 'heroes/search', component: HeroSearchComponent },
13+
{ path: 'heroes', component: HeroListComponent },
14+
{ path: 'hero/:id', component: HeroDetailComponent },
15+
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
16+
];
17+
18+
@NgModule({
19+
imports: [RouterModule.forRoot(appRoutes)],
20+
exports: [RouterModule]
21+
})
22+
export class AppRoutingModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component } from '@angular/core';
4+
5+
@Component({
6+
selector: 'my-app',
7+
template: `
8+
<h1 class="title">RxJS in Angular</h1>
9+
10+
<a routerLink="/heroes">Heroes</a><br>
11+
<a routerLink="/heroes/add">Add Hero</a><br>
12+
<a routerLink="/heroes/search">Hero Search</a>
13+
14+
<router-outlet></router-outlet>
15+
16+
<loading-component></loading-component>
17+
`
18+
})
19+
export class AppComponent {
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// #docregion
2+
import { NgModule } from '@angular/core';
3+
import { BrowserModule } from '@angular/platform-browser';
4+
import { HttpModule } from '@angular/http';
5+
import { ReactiveFormsModule } from '@angular/forms';
6+
7+
import { AppComponent } from './app.component';
8+
import { AppRoutingModule } from './app-routing.module';
9+
import { AddHeroComponent } from './add-hero.component';
10+
import { LoadingComponent } from './loading.component';
11+
import { HeroSearchComponent } from './hero-search.component';
12+
import { HeroDetailComponent } from './hero-detail.component';
13+
import { HeroListComponent } from './hero-list.component';
14+
15+
import { LoadingService } from './loading.service';
16+
import { HeroService } from './hero.service';
17+
18+
// Imports for loading & configuring the in-memory web api
19+
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
20+
import { InMemoryDataService } from './in-memory-data.service';
21+
import { ApiErrorHandlerService } from './api-error-handler.service';
22+
23+
@NgModule({
24+
imports: [
25+
BrowserModule,
26+
HttpModule,
27+
AppRoutingModule,
28+
ReactiveFormsModule,
29+
InMemoryWebApiModule.forRoot(InMemoryDataService)
30+
],
31+
declarations: [
32+
AppComponent,
33+
AddHeroComponent,
34+
LoadingComponent,
35+
HeroSearchComponent,
36+
HeroDetailComponent,
37+
HeroListComponent
38+
],
39+
providers: [
40+
HeroService,
41+
LoadingService,
42+
ApiErrorHandlerService
43+
],
44+
bootstrap: [ AppComponent ]
45+
})
46+
export class AppModule {
47+
}
48+
// #enddocregion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Injectable } from '@angular/core';
2+
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
3+
4+
export interface AppEvent {
5+
type: string;
6+
message: string;
7+
}
8+
9+
@Injectable()
10+
export class EventAggregatorService {
11+
_events: AppEvent[];
12+
events$: BehaviorSubject<AppEvent[]> = new BehaviorSubject<any>([]);
13+
14+
add(event: AppEvent) {
15+
this._events.push(event);
16+
this.next();
17+
}
18+
19+
clear() {
20+
this._events = [];
21+
this.next();
22+
}
23+
24+
next() {
25+
this.events$.next(this._events);
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/observable/of';
4+
import 'rxjs/add/operator/map';
5+
import 'rxjs/add/operator/do';
6+
import 'rxjs/add/operator/filter';
7+
import { Component, OnInit } from '@angular/core';
8+
import { ActivatedRoute, Params } from '@angular/router';
9+
import { HeroService } from './hero.service';
10+
import { Hero } from './hero';
11+
import { Observable } from 'rxjs/Observable';
12+
13+
@Component({
14+
template: `
15+
<div *ngIf="loading">
16+
Loading Hero...
17+
</div>
18+
<div *ngIf="hero">
19+
<h3>HEROES</h3>
20+
<div>
21+
<label>Id: </label>{{ hero.id }}
22+
</div>
23+
<div>
24+
<label>Name: </label>
25+
<input placeholder="name" [value]="hero.name"/>
26+
</div>
27+
</div>
28+
<div *ngIf="error">
29+
No hero found
30+
</div>
31+
`
32+
})
33+
export class HeroDetailComponent implements OnInit {
34+
hero: Hero;
35+
loading: boolean = true;
36+
error: boolean;
37+
38+
constructor(
39+
private heroService: HeroService,
40+
private route: ActivatedRoute
41+
) {}
42+
43+
ngOnInit() {
44+
this.route.params
45+
.do(() => this.loading = true)
46+
.switchMap((params: Params) =>
47+
this.heroService.getHero(params['id'])
48+
.catch(error => {
49+
this.error = true;
50+
51+
return Observable.of(null);
52+
})
53+
)
54+
.do(() => this.loading = false)
55+
.subscribe((hero: Hero) => this.hero = hero);
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/operator/toPromise';
4+
import { Component, OnInit } from '@angular/core';
5+
6+
import { HeroService } from './hero.service';
7+
import { Hero } from './hero';
8+
9+
@Component({
10+
template: `
11+
<h2>HEROES</h2>
12+
<ul class="items">
13+
<li *ngFor="let hero of heroes">
14+
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
15+
</li>
16+
</ul>
17+
`
18+
})
19+
export class HeroListComponent implements OnInit {
20+
heroes: Hero[];
21+
22+
constructor(
23+
private service: HeroService
24+
) {}
25+
26+
ngOnInit() {
27+
this.service.getHeroes()
28+
.subscribe(heroes => this.heroes = heroes);
29+
}
30+
}
31+
// #enddocregion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* #docregion */
2+
.search-result{
3+
border-bottom: 1px solid gray;
4+
border-left: 1px solid gray;
5+
border-right: 1px solid gray;
6+
width:195px;
7+
height: 20px;
8+
padding: 5px;
9+
background-color: white;
10+
cursor: pointer;
11+
}
12+
13+
.search-box{
14+
width: 200px;
15+
height: 20px;
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!-- #docregion -->
2+
<div id="search-component">
3+
<h2>HERO SEARCH</h2>
4+
<form [formGroup]="form">
5+
<input formControlName="searchTerms" class="search-box" />
6+
</form>
7+
<div>
8+
<div *ngFor="let hero of heroes$ | async" class="search-result">
9+
<a [routerLink]="['/hero', hero.id]">{{ hero.name }}</a>
10+
</div>
11+
</div>
12+
</div>

0 commit comments

Comments
 (0)