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 7668edd

Browse files
committedJan 9, 2017
docs(rxjs): Added developer guide on Observables
1 parent aff39d2 commit 7668edd

29 files changed

+929
-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,52 @@
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+
import { HeroListComponent } from './hero-list.component';
17+
18+
import { LoadingService } from './loading.service';
19+
import { HeroService } from './hero.service';
20+
21+
// Imports for loading & configuring the in-memory web api
22+
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
23+
import { InMemoryDataService } from './in-memory-data.service';
24+
25+
@NgModule({
26+
imports: [
27+
BrowserModule,
28+
HttpModule,
29+
AppRoutingModule,
30+
ReactiveFormsModule,
31+
InMemoryWebApiModule.forRoot(InMemoryDataService)
32+
],
33+
declarations: [
34+
AppComponent,
35+
HomeComponent,
36+
HeroesReadyComponent,
37+
CounterComponent,
38+
FormFieldComponent,
39+
LoadingComponent,
40+
HeroSearchComponent,
41+
HeroDetailComponent,
42+
HeroListComponent
43+
],
44+
providers: [
45+
HeroService,
46+
LoadingService
47+
],
48+
bootstrap: [ AppComponent ]
49+
})
50+
export class AppModule {
51+
}
52+
// #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,26 @@
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+
<h3>Observable Form Field</h3>
12+
<p>
13+
<input type="text" #name>
14+
<span *ngIf="blurred$ | async">Blurred</span>
15+
</p>
16+
`
17+
})
18+
export class FormFieldComponent implements OnInit {
19+
@ViewChild('name', { read: ElementRef }) name: ElementRef;
20+
21+
blurred$: Observable<boolean>;
22+
23+
ngOnInit() {
24+
this.blurred$ = Observable.fromEvent(this.name.nativeElement, 'blur');
25+
}
26+
}
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,31 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit } from '@angular/core';
4+
5+
import { HeroService } from './hero.service';
6+
import { Hero } from './hero';
7+
8+
@Component({
9+
selector: 'hero-list',
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,32 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit } from '@angular/core';
4+
5+
import { HeroService } from './hero.service';
6+
import { Hero } from './hero';
7+
8+
@Component({
9+
selector: 'hero-list',
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+
.toPromise()
29+
.then(heroes => this.heroes = heroes);
30+
}
31+
}
32+
// #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+
<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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/operator/map';
4+
import 'rxjs/add/operator/delay';
5+
import 'rxjs/add/operator/catch';
6+
import 'rxjs/add/operator/toPromise';
7+
import 'rxjs/add/observable/throw';
8+
import { Injectable } from '@angular/core';
9+
import { Http, Response } from '@angular/http';
10+
import { Observable } from 'rxjs/Observable';
11+
12+
import { Hero } from './hero';
13+
14+
@Injectable()
15+
export class HeroService {
16+
private heroesUrl = 'api/heroes';
17+
18+
constructor(private http: Http) { }
19+
20+
getHeroes(): Observable<Hero[]> {
21+
return this.http.get(this.heroesUrl)
22+
.map(response => response.json().data as Hero[]);
23+
}
24+
25+
getHeroesNow(): Promise<Hero[]> {
26+
return this.http.get(this.heroesUrl)
27+
.map(response => response.json().data as Hero[])
28+
.toPromise();
29+
}
30+
31+
getHeroesSlowly(): Observable<Hero[]> {
32+
return this.getHeroes().delay(3000);
33+
}
34+
35+
getHero(id: number): Observable<Hero> {
36+
const url = `${this.heroesUrl}/${id}`;
37+
return this.http.get(url)
38+
.map(response => response.json().data as Hero)
39+
.catch(this.handleError);
40+
}
41+
42+
getHeroesFailed() {
43+
return this.getHeroes().map(() => {
44+
throw new Error('I failed');
45+
});
46+
}
47+
48+
search(term: string): Observable<Hero[]> {
49+
return this.http
50+
.get(`app/heroes/?name=${term}`)
51+
.map((r: Response) => r.json().data as Hero[]);
52+
}
53+
54+
private handleError(error: any): Observable<any> {
55+
return Observable.throw(error);
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export class Hero {
2+
id: number;
3+
name: string;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit } from '@angular/core';
4+
import { Observable } from 'rxjs/Observable';
5+
import { Observer } from 'rxjs/Observer';
6+
7+
@Component({
8+
selector: 'heroes-ready',
9+
template: `
10+
<p>
11+
Heroes Ready: {{ counter }}
12+
13+
<button (click)="increment()">Increment</button>
14+
</p>
15+
`
16+
})
17+
export class HeroesReadyComponent implements OnInit {
18+
counter: number = 0;
19+
counter$: Observable<number>;
20+
21+
ngOnInit() {
22+
this.counter$ = Observable.create((observer: Observer<number>) => {
23+
observer.next(this.counter++);
24+
});
25+
}
26+
27+
increment() {
28+
this.counter$.subscribe(count => console.log(count));
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit } from '@angular/core';
4+
import { Observable } from 'rxjs/Observable';
5+
import { Observer } from 'rxjs/Observer';
6+
7+
@Component({
8+
selector: 'heroes-ready',
9+
template: `
10+
<p>
11+
Heroes Ready: {{ counter }}
12+
13+
<button (click)="increment()">Increment</button>
14+
</p>
15+
`
16+
})
17+
export class HeroesReadyComponent implements OnInit {
18+
counter: number = 0;
19+
counter$: Observable<number>;
20+
21+
ngOnInit() {
22+
this.counter$ = Observable.create((observer: Observer<number>) => {
23+
this.counter++;
24+
observer.next(this.counter);
25+
this.counter++;
26+
observer.next(this.counter);
27+
observer.error('test');
28+
});
29+
}
30+
31+
increment() {
32+
this.counter$.subscribe(count => console.log(count), () => console.log('error'));
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit } from '@angular/core';
4+
import { Observable } from 'rxjs/Observable';
5+
import { Observer } from 'rxjs/Observer';
6+
7+
@Component({
8+
selector: 'heroes-ready',
9+
template: `
10+
<p>
11+
Heroes Ready: {{ counter }}
12+
13+
<button (click)="increment()">Increment</button>
14+
</p>
15+
`
16+
})
17+
export class HeroesReadyComponent implements OnInit {
18+
counter: number = 0;
19+
counter$: Observable<number>;
20+
21+
ngOnInit() {
22+
this.counter$ = Observable.create((observer: Observer<number>) => {
23+
this.counter++;
24+
observer.next(this.counter);
25+
this.counter++;
26+
observer.next(this.counter);
27+
observer.complete();
28+
});
29+
}
30+
31+
increment() {
32+
this.counter$.subscribe(count => console.log(count), () => {}, () => console.log('done'));
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit } from '@angular/core';
4+
import { Observable } from 'rxjs/Observable';
5+
import { Observer } from 'rxjs/Observer';
6+
7+
@Component({
8+
selector: 'heroes-ready',
9+
template: `
10+
<p>
11+
Heroes Ready: {{ counter }}
12+
13+
<button (click)="increment()">Increment</button>
14+
</p>
15+
`
16+
})
17+
export class HeroesReadyComponent implements OnInit {
18+
counter: number = 0;
19+
counter$: Observable<number>;
20+
21+
ngOnInit() {
22+
this.counter$ = Observable.create((observer: Observer<number>) => {
23+
this.counter++;
24+
observer.next(this.counter);
25+
this.counter++;
26+
observer.next(this.counter);
27+
observer.error('test');
28+
});
29+
}
30+
31+
increment() {
32+
this.counter$.subscribe(count => console.log(count));
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
template: `
5+
<heroes-ready></heroes-ready>
6+
<counter-component></counter-component>
7+
<form-field-component></form-field-component>
8+
<hero-list></hero-list>
9+
`
10+
})
11+
export class HomeComponent {
12+
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// #docregion , init
2+
import { InMemoryDbService } from 'angular-in-memory-web-api';
3+
export class InMemoryDataService implements InMemoryDbService {
4+
createDb() {
5+
let heroes = [
6+
{id: 1, name: 'Mr. Nice'},
7+
{id: 2, name: 'Narco'},
8+
{id: 3, name: 'Bombasto'},
9+
{id: 4, name: 'Celeritas'},
10+
{id: 5, name: 'Magneta'},
11+
{id: 6, name: 'RubberMan'},
12+
{id: 7, name: 'Dynama'},
13+
{id: 8, name: 'Dr IQ'},
14+
{id: 9, name: 'Magma'},
15+
{id: 10, name: 'Tornado'}
16+
];
17+
return {heroes};
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Component, OnInit } from '@angular/core';
2+
3+
import { LoadingService } from './loading.service';
4+
5+
@Component({
6+
selector: 'loading-component',
7+
template: `
8+
<div [class.loading]="loading" *ngIf="loading">LOADING</div>
9+
`
10+
})
11+
export class LoadingComponent implements OnInit {
12+
loading: boolean;
13+
14+
constructor(private loadingService: LoadingService) {}
15+
16+
ngOnInit() {
17+
this.loadingService.loading$.subscribe(loading => {
18+
console.log(loading);
19+
this.loading = loading;
20+
});
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'rxjs/add/operator/map';
2+
import { Injectable } from '@angular/core';
3+
import { Router, Event, RoutesRecognized, NavigationStart } from '@angular/router';
4+
import { Observable } from 'rxjs/Observable';
5+
6+
@Injectable()
7+
export class LoadingService {
8+
loading$: Observable<boolean>;
9+
10+
constructor(private router: Router) {
11+
this.loading$ = this.router.events.map((event: Event) => {
12+
if ( event instanceof NavigationStart || event instanceof RoutesRecognized) {
13+
return true;
14+
} else {
15+
// return false for NavigationEnd, NavigationError and NavigationCancel events
16+
return false;
17+
}
18+
});
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// #docregion
2+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3+
4+
import { AppModule } from './app.module';
5+
6+
platformBrowserDynamic().bootstrapModule(AppModule);

‎public/docs/_examples/rxjs/ts/example-config.json

Whitespace-only changes.
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html>
2+
<!-- #docregion -->
3+
<html>
4+
<head>
5+
<!-- Set the base href -->
6+
<base href="/">
7+
<title>RxJS Observables</title>
8+
<meta charset="UTF-8">
9+
<meta name="viewport" content="width=device-width, initial-scale=1">
10+
<link rel="stylesheet" href="styles.css">
11+
12+
<!-- Polyfills for older browsers -->
13+
<script src="node_modules/core-js/client/shim.min.js"></script>
14+
15+
<script src="node_modules/zone.js/dist/zone.js"></script>
16+
<script src="node_modules/reflect-metadata/Reflect.js"></script>
17+
<script src="node_modules/systemjs/dist/system.src.js"></script>
18+
19+
<script src="systemjs.config.js"></script>
20+
<script>
21+
System.import('app')
22+
.catch(function(err){ console.error(err); });
23+
</script>
24+
</head>
25+
26+
<body>
27+
<my-app>loading...</my-app>
28+
</body>
29+
30+
</html>
31+
<!-- #enddocregion -->
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"description": "RxJS",
3+
"files":[
4+
"!**/*.d.ts",
5+
"!**/*.js",
6+
"!**/*.[0-9].*"
7+
],
8+
"tags": ["rxjs", "observable"]
9+
}

‎public/docs/ts/latest/guide/rxjs.jade

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
block includes
2+
include ../_util-fns
3+
4+
:marked
5+
**Observables** provided by the Reactive Extensions for Javascript (RxJS) library provide applications with an extensive API
6+
for handling asynchronous and event-based values produced over time.
7+
8+
An application is made up of many different streams of information. Whether it be user input into
9+
a form, navigating from one route another, making an HTTP request to fetch some data, updating the application view with
10+
new data as its received, or many other examples, each of these events happen over time. Observables provide a interface to
11+
handle the many different sources of events and help to transform these events as they flow throughout an application.
12+
13+
This guide will serve as an introductory chapter to Observables, common uses cases for Observables in an Angular application
14+
and how Observables are used and provided by the Angular framework.
15+
16+
## Table of Contents
17+
* [The Observable](#definition "")
18+
* [Observables and Promises](#promises "")
19+
* [Observable Lifecycle](#lifecycle "")
20+
* [Observer](#lifecycle "")
21+
* [Subscriptions](#subscriptions "")
22+
* [Creating Observables](#creating "")
23+
* [Operators](#transforming "")
24+
* [Error Handling](#error-handling "")
25+
* [Framework APIs](#apis "")
26+
* [Impact on Change Detection](#change-detection "")
27+
* [Further Reading](#reading "")
28+
29+
:marked
30+
The Observable: a function at its core
31+
32+
An Observable, simply put, is a specific type of function with a specific purpose. Its a function that accepts an `Observer` to produce values and
33+
returns a function for cancellation. It represents an action that can be performed. This action may be performed right now, or at some point
34+
in the future.
35+
36+
An action can be anything, from simply "return a constant" to "make an HTTP request". Here’s a simple "action" function that increments a counter
37+
and returns the new value.
38+
39+
// Example of simple counter
40+
41+
The same functionality can be produced with an Observable. Observables don't return values directly, as they can be produced synchronously asynchronously.
42+
An Observer is used by an Observable to consume its produced values.
43+
44+
// Example of observable counter
45+
46+
There is a key difference between these two examples. In the first counter example, the results of the action were produced when the function was called.
47+
In the Observable counter, the Observable was created, with the [Observer](https://en.wikipedia.org/wiki/Observer_pattern) to produce values and the value incremented but the action hasn't been performed yet.
48+
It represents an action we've defined, but it hasn't been executed.
49+
50+
// Example of subscribing to observable counter
51+
52+
Observable streams are **cold** or **lazy** by nature, meaning that until you invoke them, they will not produce
53+
any values. Invoking an Observable is done using the `subscribe` method, which makes the Observable **hot** and calls the observer's method to produce
54+
values. The `subscribe` function also returns an object with an `unsubscribe` from an Observable and no longer receive the values it produces.
55+
56+
// Example of unsubscribing
57+
58+
Managing the lifecycle of an Observable will be discussed later in the chapter.
59+
60+
:marked
61+
Observables and Promises: More different than alike
62+
63+
RxJS and Observables have been around for a long time, and they aren't the first concept of handling asynchronous events. Before Observables became more prevalent,
64+
the `Promise` was the primary way of handling asynchronous events. Promises and Observables share some similarities as they both handle asynchronous events,
65+
both implement a function to handle execution and error handling, but they are more different then alike.
66+
67+
Promises
68+
* Produce a one-time value
69+
* Can be composed
70+
* Are always resolved/rejected asynchronously
71+
* Are always multicast to multiple receivers
72+
73+
Observables
74+
* Produce a number of values over time
75+
* Can be composed
76+
* Resolve synchronously/asynchronously
77+
* Multicast when needed
78+
79+
The strength of Observables is producing and handling values over time, which is something a Promise wasn't designed to do. Observables also provide mechanisms
80+
for easy cancellation, retrying upon failure and transformations. Observables include a rich library of operators, along with the extensibility to provide a more powerful
81+
tool to handle the various streams of events in an application. So does this mean Promises are no longer needed? Absolutely not. Promises will continue to serve a purpose as the right tool for the job in some situations.
82+
The good news is Observables support conversion to a Promise for instances where a one-off value needs to be produced without managing the lifecycle
83+
of an Observable.
84+
85+
:marked
86+
Observable Anatomy: Next, Error, and Complete
87+
88+
Even though an Observable is a function that performs an action, it has a lifecycle. An Observable has 3 types of notifications it produces through its
89+
lifetime: `next`, `error`, and `complete`.
90+
91+
The `next` is called whenever the observable produces a new value, notifying the Observer
92+
that some value was produced. The `next` Observables may produce a single value, or multiple values over time. These values can be a number, string, object
93+
94+
95+
Let's modify the HeroesReadyComponent to produce two values when subscribed.
96+
97+
// Example of Observable that produces 2 values
98+
99+
When click the `increment` button this time and subscribes to the Observable, the counter will be incremented twice and produce two values.
100+
101+
Whenever an Observable produces an error, it uses the `error` event. An error event can only happen once during the invocation of an Observable action.
102+
103+
// Example of Observable that errors
104+
105+
The Observable notifies its Observer through that an error has occurred.
106+
107+
Observables can also notify observers of completion. A completion event signals that no more values will be produced by the Observable. Like the error event,
108+
a completion event can only happen once.
109+
110+
// Example of Observable that produces values, then completes
111+
112+
:marked
113+
Observer: The Observable's Consumer
114+
An Observer is provided to an Observable to consume its values produced. An observer provides callbacks for the notification types produced by an Observable: `next`,
115+
`error`, and `complete`.
116+
117+
An Observer is provided to an Observable through the `subscribe` method in 2 ways
118+
119+
* As a single object containing 3 callbacks for each notification.
120+
* As 3 arguments in the subscribe method for each notification
121+
122+
// Example of single object and logging out each notification
123+
124+
// Example of 3 arguments to subscribe method
125+
126+
:marked
127+
Subscription: Maintaining of resources
128+
129+
As mentioned earlier, an Observable is not invoked until its `subscribed` to. This starts the execution of the Observable to produce values or events to an Observer.
130+
This subscription is an allocation of resources for the action performed by the Observable. Naturally, you want to clean up resources used by the Observable when
131+
finished with its execution. Each time an Observable is subscribed, it returns a `Subscription`. The `Subscription` is an object that handles the resources provided
132+
by the Observable, along with the `unsubscribe`. The `unsubscribe` method provided by the `Subscription` disposes of the resources allocated by the Observable.
133+
134+
// Example of unsubscribing
135+
136+
As a general rule and good practice, resources that are allocated and used must be cleaned up, and the Observable subscription is no different. Angular provides
137+
APIs that manage Observable lifecycles without the need to unsubscribe, but for those Observables you create, cleanup of resources is a must to protect against
138+
memory leaks and other unwanted side effects.
139+
140+
// Example of unsubscribe in ngOnDestroy
141+
142+
In this example, only one subscription is used and disposed of. Managing multiple subscriptions using the `unsubscribe` method
143+
has the potential to get unwieldy. You'll learn about different ways to to end subscriptions of multiple Observables later in the chapter.
144+
145+
// Example of unsubscribe using a Subject
146+
147+
:marked
148+
Operators: Observable functions
149+
150+
The Observable prototype contains many `operators`, which are methods that perform an operation. Operators are **pure functions**
151+
that are stateless. Stateless operators are less error-prone as they are provided an Observable, in which an operation is performed on,
152+
and return a new Observable, without modifying the original Observable. The Observable prototype comes with a minimal set of operators by default,
153+
with a large set of operators that can be added. Operators are also used to (create Observable instances) from existing events.
154+
155+
There are different ways to access the operators provided by RxJS: By patching the Observable prototype or by importing the operator functions directly.
156+
Each operator only needs to be patched on the prototype once, but where you choose to patch the Observable requires consideration. We'll examine a
157+
few options below:
158+
159+
// Example of importing entire rxjs/Rx library
160+
161+
By importing `rxjs/Rx`, the **entire** set of RxJS operators are added to the Observable prototype. In most cases, you will only use a subset of
162+
the available operators and adding the entire set increases the overall bundle size of your application. This is
163+
only recommended for sample apps, and in testing environments.
164+
165+
// Example of patching the Observable prototype
166+
167+
Since minimizing bundle size is a recommended practice, you should only import the operators you need, where you need them.
168+
This approach includes importing operators multiple times in different files, but safeguards against using operators
169+
without having patched the Observable prototype first. Since feature areas can be loaded lazily, this also allows you the benefit
170+
of keeping certain operators in separate bundles and only loaded them when needed.
171+
172+
// Example of importing operators directly
173+
174+
// Note about tree-shaking when pat
175+
176+
NOTE: Operators patched onto the Observable prototype that are unused will not be tree-shaken.
177+
178+
Operators can also be directly imported and used without patching the Observable prototype. This is the
179+
recommended option for third-party libraries, staying with the approach to only import what you need. It
180+
is also more suited for tree-shaking when bundling your Angular application.
181+
182+
:marked

0 commit comments

Comments
 (0)
This repository has been archived.