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

Commit aa3e1e6

Browse files
docs(rxjs): Added developer guide on Observables
1 parent aff39d2 commit aa3e1e6

27 files changed

+1023
-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,19 @@
1+
// #docplaster
2+
// #docregion
3+
import { NgModule } from '@angular/core';
4+
import { RouterModule, Routes } from '@angular/router';
5+
import { HomeComponent } from './home.component';
6+
import { HeroDetailComponent } from './hero-detail.component';
7+
import { HeroSearchComponent } from './hero-search.component';
8+
9+
const appRoutes: Routes = [
10+
{ path: '', component: HomeComponent },
11+
{ path: 'hero/search', component: HeroSearchComponent },
12+
{ path: 'hero/:id', component: HeroDetailComponent }
13+
];
14+
15+
@NgModule({
16+
imports: [RouterModule.forRoot(appRoutes)],
17+
exports: [RouterModule]
18+
})
19+
export class AppRoutingModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
<router-outlet></router-outlet>
11+
<loading-component></loading-component>
12+
`
13+
})
14+
export class AppComponent {
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 { HomeComponent } from './home.component';
10+
import { HeroesReadyComponent } from './heroes-ready.component';
11+
import { CounterComponent } from './counter.component';
12+
import { FormFieldComponent } from './form-field.component';
13+
import { LoadingComponent } from './loading.component';
14+
import { HeroSearchComponent } from './hero-search.component';
15+
import { HeroDetailComponent } from './hero-detail.component';
16+
17+
import { LoadingService } from './loading.service';
18+
import { HeroService } from './hero.service';
19+
20+
// Imports for loading & configuring the in-memory web api
21+
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
22+
import { InMemoryDataService } from './in-memory-data.service';
23+
24+
@NgModule({
25+
imports: [
26+
BrowserModule,
27+
HttpModule,
28+
AppRoutingModule,
29+
ReactiveFormsModule,
30+
InMemoryWebApiModule.forRoot(InMemoryDataService)
31+
],
32+
declarations: [
33+
AppComponent,
34+
HomeComponent,
35+
HeroesReadyComponent,
36+
CounterComponent,
37+
FormFieldComponent,
38+
LoadingComponent,
39+
HeroSearchComponent,
40+
HeroDetailComponent
41+
],
42+
providers: [
43+
HeroService,
44+
LoadingService
45+
],
46+
bootstrap: [ AppComponent ]
47+
})
48+
export class AppModule {
49+
}
50+
// #enddocregion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit, OnDestroy } from '@angular/core';
4+
import { Subject } from 'rxjs/Subject';
5+
import { Subscription } from 'rxjs/Subscription';
6+
7+
@Component({
8+
selector: 'counter-component',
9+
template: `
10+
<p>
11+
Hero Counter: {{ count }}
12+
13+
<button (click)="increment()">Increment</button>
14+
</p>
15+
`
16+
})
17+
export class CounterComponent implements OnInit, OnDestroy {
18+
count: number = 0;
19+
counter$ = new Subject();
20+
sub: Subscription;
21+
22+
ngOnInit() {
23+
this.sub = this.counter$.subscribe();
24+
}
25+
26+
increment() {
27+
this.counter$.next(this.count++);
28+
}
29+
30+
ngOnDestroy() {
31+
this.sub.unsubscribe();
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/operator/takeUntil';
4+
import { Component, OnInit, OnDestroy } from '@angular/core';
5+
import { Subject } from 'rxjs/Subject';
6+
7+
@Component({
8+
selector: 'counter-component',
9+
template: `
10+
<p>
11+
Counter: {{ count }}
12+
13+
<button (click)="increment()">Increment</button>
14+
</p>
15+
`
16+
})
17+
export class CounterComponent implements OnInit, OnDestroy {
18+
count: number = 0;
19+
counter$ = new Subject();
20+
destroy$: Subject<any> = new Subject();
21+
22+
ngOnInit() {
23+
this.counter$.takeUntil(this.destroy$).subscribe();
24+
}
25+
26+
increment() {
27+
this.counter$.next(this.count++);
28+
}
29+
30+
ngOnDestroy() {
31+
this.destroy$.next();
32+
}
33+
}
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 Event {
5+
type: string;
6+
message: string;
7+
}
8+
9+
@Injectable()
10+
export class EventAggregatorService {
11+
_events: Event[];
12+
events$: BehaviorSubject<Event[]> = new BehaviorSubject<any>([]);
13+
14+
add(event: Event) {
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,25 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/observable/of';
4+
import 'rxjs/add/observable/fromEvent';
5+
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
6+
import { Observable } from 'rxjs/Observable';
7+
8+
@Component({
9+
selector: 'form-field-component',
10+
template: `
11+
<p>
12+
<input type="text" #name>
13+
<span *ngIf="blurred$ | async">Blurred</span>
14+
</p>
15+
`
16+
})
17+
export class FormFieldComponent implements OnInit {
18+
@ViewChild('name', { read: ElementRef }) name: ElementRef;
19+
20+
blurred$: Observable<boolean>;
21+
22+
ngOnInit() {
23+
this.blurred$ = Observable.fromEvent(this.name.nativeElement, 'blur');
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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="loaded && 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="loaded && !hero">
29+
No hero found
30+
</div>
31+
`
32+
})
33+
export class HeroDetailComponent implements OnInit {
34+
hero: Hero;
35+
loading: boolean = true;
36+
loaded: boolean;
37+
38+
constructor(
39+
private heroService: HeroService,
40+
private route: ActivatedRoute
41+
) {}
42+
43+
ngOnInit() {
44+
this.route.params
45+
.do(() => {
46+
this.loading = true;
47+
this.loaded = false;
48+
})
49+
.switchMap((params: Params) =>
50+
this.heroService.getHero(params['id'])
51+
.catch(() => Observable.of(null))
52+
)
53+
.do(() => {
54+
this.loading = false;
55+
this.loaded = true;
56+
})
57+
.subscribe(hero => this.hero = hero);
58+
}
59+
}
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+
<h4>Hero Search</h4>
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>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/operator/debounceTime';
4+
import 'rxjs/add/operator/distinctUntilChanged';
5+
import 'rxjs/add/operator/do';
6+
import 'rxjs/add/operator/filter';
7+
import 'rxjs/add/operator/switchMap';
8+
import 'rxjs/add/observable/merge';
9+
import { Component, OnInit, OnDestroy } from '@angular/core';
10+
import { FormBuilder, FormGroup } from '@angular/forms';
11+
import { Router, ActivatedRoute, Params } from '@angular/router';
12+
import { Observable } from 'rxjs/Observable';
13+
import { Subject } from 'rxjs/Subject';
14+
15+
import { HeroService } from './hero.service';
16+
import { Hero } from './hero';
17+
18+
@Component({
19+
moduleId: module.id,
20+
selector: 'hero-search',
21+
templateUrl: 'hero-search.component.html',
22+
styleUrls: [ 'hero-search.component.css' ]
23+
})
24+
export class HeroSearchComponent implements OnInit, OnDestroy {
25+
heroes$: Observable<Hero[]>;
26+
destroy$: Subject<any> = new Subject();
27+
form: FormGroup;
28+
29+
constructor(
30+
private heroService: HeroService,
31+
private formBuilder: FormBuilder,
32+
private route: ActivatedRoute,
33+
private router: Router
34+
) {}
35+
36+
ngOnInit(): void {
37+
this.form = this.formBuilder.group({
38+
searchTerms: ['']
39+
});
40+
41+
const searchTerms$: Observable<string> = this.form.valueChanges
42+
.debounceTime(300)
43+
.map(model => model.searchTerms);
44+
45+
const querySearch$: Observable<string> = this.route.queryParams
46+
.map((params: Params) => params['q'])
47+
.do(searchTerms => this.form.patchValue({
48+
searchTerms
49+
}));
50+
51+
this.heroes$ = Observable.merge(searchTerms$, querySearch$)
52+
.distinctUntilChanged()
53+
.takeUntil(this.destroy$)
54+
.do(q => this.router.navigate(['./'], { queryParams: { q }, relativeTo: this.route }))
55+
.switchMap(term => term
56+
? this.heroService.search(term)
57+
: Observable.of<Hero[]>([])
58+
)
59+
.catch(error => {
60+
return Observable.of<Hero[]>([]);
61+
});
62+
}
63+
64+
ngOnDestroy() {
65+
this.destroy$.next();
66+
}
67+
}

0 commit comments

Comments
 (0)