From aa3e1e64aeed92fe608047c80971bf39a9070b95 Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Tue, 29 Nov 2016 21:58:44 -0600 Subject: [PATCH] docs(rxjs): Added developer guide on Observables --- public/docs/_examples/rxjs/e2e-spec.ts | 12 + .../rxjs/ts/app/app-routing.module.ts | 19 + .../_examples/rxjs/ts/app/app.component.ts | 15 + .../docs/_examples/rxjs/ts/app/app.module.ts | 50 +++ .../rxjs/ts/app/counter.component.1.ts | 33 ++ .../rxjs/ts/app/counter.component.ts | 33 ++ .../rxjs/ts/app/event-aggregator.service.ts | 27 ++ .../rxjs/ts/app/form-field.component.ts | 25 ++ .../rxjs/ts/app/hero-detail.component.ts | 59 +++ .../rxjs/ts/app/hero-search.component.css | 16 + .../rxjs/ts/app/hero-search.component.html | 12 + .../rxjs/ts/app/hero-search.component.ts | 67 ++++ .../_examples/rxjs/ts/app/hero.service.ts | 57 +++ public/docs/_examples/rxjs/ts/app/hero.ts | 4 + .../rxjs/ts/app/heroes-ready.component.1.ts | 30 ++ .../rxjs/ts/app/heroes-ready.component.2.ts | 34 ++ .../rxjs/ts/app/heroes-ready.component.3.ts | 34 ++ .../rxjs/ts/app/heroes-ready.component.ts | 34 ++ .../_examples/rxjs/ts/app/home.component.ts | 12 + .../rxjs/ts/app/in-memory-data.service.ts | 19 + .../rxjs/ts/app/loading.component.ts | 22 ++ .../_examples/rxjs/ts/app/loading.service.ts | 20 + public/docs/_examples/rxjs/ts/app/main.ts | 6 + .../_examples/rxjs/ts/example-config.json | 0 public/docs/_examples/rxjs/ts/index.html | 31 ++ public/docs/_examples/rxjs/ts/plnkr.json | 9 + public/docs/ts/latest/guide/rxjs.jade | 343 ++++++++++++++++++ 27 files changed, 1023 insertions(+) create mode 100644 public/docs/_examples/rxjs/e2e-spec.ts create mode 100644 public/docs/_examples/rxjs/ts/app/app-routing.module.ts create mode 100644 public/docs/_examples/rxjs/ts/app/app.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/app.module.ts create mode 100644 public/docs/_examples/rxjs/ts/app/counter.component.1.ts create mode 100644 public/docs/_examples/rxjs/ts/app/counter.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/event-aggregator.service.ts create mode 100644 public/docs/_examples/rxjs/ts/app/form-field.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/hero-detail.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/hero-search.component.css create mode 100644 public/docs/_examples/rxjs/ts/app/hero-search.component.html create mode 100644 public/docs/_examples/rxjs/ts/app/hero-search.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/hero.service.ts create mode 100644 public/docs/_examples/rxjs/ts/app/hero.ts create mode 100644 public/docs/_examples/rxjs/ts/app/heroes-ready.component.1.ts create mode 100644 public/docs/_examples/rxjs/ts/app/heroes-ready.component.2.ts create mode 100644 public/docs/_examples/rxjs/ts/app/heroes-ready.component.3.ts create mode 100644 public/docs/_examples/rxjs/ts/app/heroes-ready.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/home.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/in-memory-data.service.ts create mode 100644 public/docs/_examples/rxjs/ts/app/loading.component.ts create mode 100644 public/docs/_examples/rxjs/ts/app/loading.service.ts create mode 100644 public/docs/_examples/rxjs/ts/app/main.ts create mode 100644 public/docs/_examples/rxjs/ts/example-config.json create mode 100644 public/docs/_examples/rxjs/ts/index.html create mode 100644 public/docs/_examples/rxjs/ts/plnkr.json create mode 100644 public/docs/ts/latest/guide/rxjs.jade diff --git a/public/docs/_examples/rxjs/e2e-spec.ts b/public/docs/_examples/rxjs/e2e-spec.ts new file mode 100644 index 0000000000..adb6e5e9be --- /dev/null +++ b/public/docs/_examples/rxjs/e2e-spec.ts @@ -0,0 +1,12 @@ +'use strict'; // necessary for es6 output in node + +import { browser +/*, element, by, ElementFinder*/ +} from 'protractor'; + +describe('RxJS', function () { + + beforeAll(function () { + browser.get(''); + }); +}); diff --git a/public/docs/_examples/rxjs/ts/app/app-routing.module.ts b/public/docs/_examples/rxjs/ts/app/app-routing.module.ts new file mode 100644 index 0000000000..5a950e7176 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/app-routing.module.ts @@ -0,0 +1,19 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { HomeComponent } from './home.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroSearchComponent } from './hero-search.component'; + +const appRoutes: Routes = [ + { path: '', component: HomeComponent }, + { path: 'hero/search', component: HeroSearchComponent }, + { path: 'hero/:id', component: HeroDetailComponent } +]; + +@NgModule({ + imports: [RouterModule.forRoot(appRoutes)], + exports: [RouterModule] +}) +export class AppRoutingModule {} diff --git a/public/docs/_examples/rxjs/ts/app/app.component.ts b/public/docs/_examples/rxjs/ts/app/app.component.ts new file mode 100644 index 0000000000..2b75999678 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/app.component.ts @@ -0,0 +1,15 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

RxJS in Angular

+ + + + ` +}) +export class AppComponent { +} diff --git a/public/docs/_examples/rxjs/ts/app/app.module.ts b/public/docs/_examples/rxjs/ts/app/app.module.ts new file mode 100644 index 0000000000..3a99e914e4 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/app.module.ts @@ -0,0 +1,50 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { HttpModule } from '@angular/http'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; +import { HomeComponent } from './home.component'; +import { HeroesReadyComponent } from './heroes-ready.component'; +import { CounterComponent } from './counter.component'; +import { FormFieldComponent } from './form-field.component'; +import { LoadingComponent } from './loading.component'; +import { HeroSearchComponent } from './hero-search.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +import { LoadingService } from './loading.service'; +import { HeroService } from './hero.service'; + +// Imports for loading & configuring the in-memory web api +import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; +import { InMemoryDataService } from './in-memory-data.service'; + +@NgModule({ + imports: [ + BrowserModule, + HttpModule, + AppRoutingModule, + ReactiveFormsModule, + InMemoryWebApiModule.forRoot(InMemoryDataService) + ], + declarations: [ + AppComponent, + HomeComponent, + HeroesReadyComponent, + CounterComponent, + FormFieldComponent, + LoadingComponent, + HeroSearchComponent, + HeroDetailComponent + ], + providers: [ + HeroService, + LoadingService + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { +} +// #enddocregion diff --git a/public/docs/_examples/rxjs/ts/app/counter.component.1.ts b/public/docs/_examples/rxjs/ts/app/counter.component.1.ts new file mode 100644 index 0000000000..92000b4beb --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/counter.component.1.ts @@ -0,0 +1,33 @@ +// #docplaster +// #docregion +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; +import { Subscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'counter-component', + template: ` +

+ Hero Counter: {{ count }} + + +

+ ` +}) +export class CounterComponent implements OnInit, OnDestroy { + count: number = 0; + counter$ = new Subject(); + sub: Subscription; + + ngOnInit() { + this.sub = this.counter$.subscribe(); + } + + increment() { + this.counter$.next(this.count++); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/counter.component.ts b/public/docs/_examples/rxjs/ts/app/counter.component.ts new file mode 100644 index 0000000000..3c163bd523 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/counter.component.ts @@ -0,0 +1,33 @@ +// #docplaster +// #docregion +import 'rxjs/add/operator/takeUntil'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; + +@Component({ + selector: 'counter-component', + template: ` +

+ Counter: {{ count }} + + +

+ ` +}) +export class CounterComponent implements OnInit, OnDestroy { + count: number = 0; + counter$ = new Subject(); + destroy$: Subject = new Subject(); + + ngOnInit() { + this.counter$.takeUntil(this.destroy$).subscribe(); + } + + increment() { + this.counter$.next(this.count++); + } + + ngOnDestroy() { + this.destroy$.next(); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/event-aggregator.service.ts b/public/docs/_examples/rxjs/ts/app/event-aggregator.service.ts new file mode 100644 index 0000000000..941a22254f --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/event-aggregator.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export interface Event { + type: string; + message: string; +} + +@Injectable() +export class EventAggregatorService { + _events: Event[]; + events$: BehaviorSubject = new BehaviorSubject([]); + + add(event: Event) { + this._events.push(event); + this.next(); + } + + clear() { + this._events = []; + this.next(); + } + + next() { + this.events$.next(this._events); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/form-field.component.ts b/public/docs/_examples/rxjs/ts/app/form-field.component.ts new file mode 100644 index 0000000000..004c3654b0 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/form-field.component.ts @@ -0,0 +1,25 @@ +// #docplaster +// #docregion +import 'rxjs/add/observable/of'; +import 'rxjs/add/observable/fromEvent'; +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +@Component({ + selector: 'form-field-component', + template: ` +

+ + Blurred +

+ ` +}) +export class FormFieldComponent implements OnInit { + @ViewChild('name', { read: ElementRef }) name: ElementRef; + + blurred$: Observable; + + ngOnInit() { + this.blurred$ = Observable.fromEvent(this.name.nativeElement, 'blur'); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/hero-detail.component.ts b/public/docs/_examples/rxjs/ts/app/hero-detail.component.ts new file mode 100644 index 0000000000..7e39ac17e4 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/hero-detail.component.ts @@ -0,0 +1,59 @@ +// #docplaster +// #docregion +import 'rxjs/add/observable/of'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/filter'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; +import { HeroService } from './hero.service'; +import { Hero } from './hero'; +import { Observable } from 'rxjs/Observable'; + +@Component({ + template: ` +
+ Loading Hero... +
+
+

HEROES

+
+ {{ hero.id }} +
+
+ + +
+
+
+ No hero found +
+ ` +}) +export class HeroDetailComponent implements OnInit { + hero: Hero; + loading: boolean = true; + loaded: boolean; + + constructor( + private heroService: HeroService, + private route: ActivatedRoute + ) {} + + ngOnInit() { + this.route.params + .do(() => { + this.loading = true; + this.loaded = false; + }) + .switchMap((params: Params) => + this.heroService.getHero(params['id']) + .catch(() => Observable.of(null)) + ) + .do(() => { + this.loading = false; + this.loaded = true; + }) + .subscribe(hero => this.hero = hero); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/hero-search.component.css b/public/docs/_examples/rxjs/ts/app/hero-search.component.css new file mode 100644 index 0000000000..51e3ca4370 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/hero-search.component.css @@ -0,0 +1,16 @@ +/* #docregion */ +.search-result{ + border-bottom: 1px solid gray; + border-left: 1px solid gray; + border-right: 1px solid gray; + width:195px; + height: 20px; + padding: 5px; + background-color: white; + cursor: pointer; +} + +.search-box{ + width: 200px; + height: 20px; +} diff --git a/public/docs/_examples/rxjs/ts/app/hero-search.component.html b/public/docs/_examples/rxjs/ts/app/hero-search.component.html new file mode 100644 index 0000000000..d2f5edfaff --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/hero-search.component.html @@ -0,0 +1,12 @@ + +
+

Hero Search

+
+ +
+
+ +
+
diff --git a/public/docs/_examples/rxjs/ts/app/hero-search.component.ts b/public/docs/_examples/rxjs/ts/app/hero-search.component.ts new file mode 100644 index 0000000000..f334f0b1a7 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/hero-search.component.ts @@ -0,0 +1,67 @@ +// #docplaster +// #docregion +import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/distinctUntilChanged'; +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/switchMap'; +import 'rxjs/add/observable/merge'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Router, ActivatedRoute, Params } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; + +import { HeroService } from './hero.service'; +import { Hero } from './hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-search', + templateUrl: 'hero-search.component.html', + styleUrls: [ 'hero-search.component.css' ] +}) +export class HeroSearchComponent implements OnInit, OnDestroy { + heroes$: Observable; + destroy$: Subject = new Subject(); + form: FormGroup; + + constructor( + private heroService: HeroService, + private formBuilder: FormBuilder, + private route: ActivatedRoute, + private router: Router + ) {} + + ngOnInit(): void { + this.form = this.formBuilder.group({ + searchTerms: [''] + }); + + const searchTerms$: Observable = this.form.valueChanges + .debounceTime(300) + .map(model => model.searchTerms); + + const querySearch$: Observable = this.route.queryParams + .map((params: Params) => params['q']) + .do(searchTerms => this.form.patchValue({ + searchTerms + })); + + this.heroes$ = Observable.merge(searchTerms$, querySearch$) + .distinctUntilChanged() + .takeUntil(this.destroy$) + .do(q => this.router.navigate(['./'], { queryParams: { q }, relativeTo: this.route })) + .switchMap(term => term + ? this.heroService.search(term) + : Observable.of([]) + ) + .catch(error => { + return Observable.of([]); + }); + } + + ngOnDestroy() { + this.destroy$.next(); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/hero.service.ts b/public/docs/_examples/rxjs/ts/app/hero.service.ts new file mode 100644 index 0000000000..69b18f11bb --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/hero.service.ts @@ -0,0 +1,57 @@ +// #docplaster +// #docregion +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/delay'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/toPromise'; +import 'rxjs/add/observable/throw'; +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; + +import { Hero } from './hero'; + +@Injectable() +export class HeroService { + private heroesUrl = 'api/heroes'; + + constructor(private http: Http) { } + + getHeroes(): Observable { + return this.http.get(this.heroesUrl) + .map(response => response.json().data as Hero[]); + } + + getHeroesNow(): Promise { + return this.http.get(this.heroesUrl) + .map(response => response.json().data as Hero[]) + .toPromise(); + } + + getHeroesSlowly(): Observable { + return this.getHeroes().delay(3000); + } + + getHero(id: number): Observable { + const url = `${this.heroesUrl}/${id}`; + return this.http.get(url) + .map(response => response.json().data as Hero) + .catch(this.handleError); + } + + getHeroesFailed() { + return this.getHeroes().map(() => { + throw new Error('I failed'); + }); + } + + search(term: string): Observable { + return this.http + .get(`app/heroes/?name=${term}`) + .map((r: Response) => r.json().data as Hero[]); + } + + private handleError(error: any): Observable { + return Observable.throw(error); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/hero.ts b/public/docs/_examples/rxjs/ts/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/public/docs/_examples/rxjs/ts/app/heroes-ready.component.1.ts b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.1.ts new file mode 100644 index 0000000000..ce0e6cd621 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.1.ts @@ -0,0 +1,30 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Observer } from 'rxjs/Observer'; + +@Component({ + selector: 'heroes-ready', + template: ` +

+ Heroes Ready: {{ counter }} + + +

+ ` +}) +export class HeroesReadyComponent implements OnInit { + counter: number = 0; + counter$: Observable; + + ngOnInit() { + this.counter$ = Observable.create((observer: Observer) => { + observer.next(this.counter++); + }); + } + + increment() { + this.counter$.subscribe(count => console.log(count)); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/heroes-ready.component.2.ts b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.2.ts new file mode 100644 index 0000000000..58f664acf6 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.2.ts @@ -0,0 +1,34 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Observer } from 'rxjs/Observer'; + +@Component({ + selector: 'heroes-ready', + template: ` +

+ Heroes Ready: {{ counter }} + + +

+ ` +}) +export class HeroesReadyComponent implements OnInit { + counter: number = 0; + counter$: Observable; + + ngOnInit() { + this.counter$ = Observable.create((observer: Observer) => { + this.counter++; + observer.next(this.counter); + this.counter++; + observer.next(this.counter); + observer.error('test'); + }); + } + + increment() { + this.counter$.subscribe(count => console.log(count), () => console.log('error')); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/heroes-ready.component.3.ts b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.3.ts new file mode 100644 index 0000000000..ec89823df5 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.3.ts @@ -0,0 +1,34 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Observer } from 'rxjs/Observer'; + +@Component({ + selector: 'heroes-ready', + template: ` +

+ Heroes Ready: {{ counter }} + + +

+ ` +}) +export class HeroesReadyComponent implements OnInit { + counter: number = 0; + counter$: Observable; + + ngOnInit() { + this.counter$ = Observable.create((observer: Observer) => { + this.counter++; + observer.next(this.counter); + this.counter++; + observer.next(this.counter); + observer.complete(); + }); + } + + increment() { + this.counter$.subscribe(count => console.log(count), () => () => console.log('done')); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/heroes-ready.component.ts b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.ts new file mode 100644 index 0000000000..e4df00fb97 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/heroes-ready.component.ts @@ -0,0 +1,34 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Observer } from 'rxjs/Observer'; + +@Component({ + selector: 'heroes-ready', + template: ` +

+ Heroes Ready: {{ counter }} + + +

+ ` +}) +export class HeroesReadyComponent implements OnInit { + counter: number = 0; + counter$: Observable; + + ngOnInit() { + this.counter$ = Observable.create((observer: Observer) => { + this.counter++; + observer.next(this.counter); + this.counter++; + observer.next(this.counter); + observer.error('test'); + }); + } + + increment() { + this.counter$.subscribe(count => console.log(count)); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/home.component.ts b/public/docs/_examples/rxjs/ts/app/home.component.ts new file mode 100644 index 0000000000..0b3944b114 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/home.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + template: ` + + + + ` +}) +export class HomeComponent { + +} diff --git a/public/docs/_examples/rxjs/ts/app/in-memory-data.service.ts b/public/docs/_examples/rxjs/ts/app/in-memory-data.service.ts new file mode 100644 index 0000000000..9b227bf2d3 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/in-memory-data.service.ts @@ -0,0 +1,19 @@ +// #docregion , init +import { InMemoryDbService } from 'angular-in-memory-web-api'; +export class InMemoryDataService implements InMemoryDbService { + createDb() { + let heroes = [ + {id: 1, name: 'Mr. Nice'}, + {id: 2, name: 'Narco'}, + {id: 3, name: 'Bombasto'}, + {id: 4, name: 'Celeritas'}, + {id: 5, name: 'Magneta'}, + {id: 6, name: 'RubberMan'}, + {id: 7, name: 'Dynama'}, + {id: 8, name: 'Dr IQ'}, + {id: 9, name: 'Magma'}, + {id: 10, name: 'Tornado'} + ]; + return {heroes}; + } +} diff --git a/public/docs/_examples/rxjs/ts/app/loading.component.ts b/public/docs/_examples/rxjs/ts/app/loading.component.ts new file mode 100644 index 0000000000..5931fb3512 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/loading.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; + +import { LoadingService } from './loading.service'; + +@Component({ + selector: 'loading-component', + template: ` +
LOADING
+ ` +}) +export class LoadingComponent implements OnInit { + loading: boolean; + + constructor(private loadingService: LoadingService) {} + + ngOnInit() { + this.loadingService.loading$.subscribe(loading => { + console.log(loading); + this.loading = loading; + }); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/loading.service.ts b/public/docs/_examples/rxjs/ts/app/loading.service.ts new file mode 100644 index 0000000000..82e84aeb7b --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/loading.service.ts @@ -0,0 +1,20 @@ +import 'rxjs/add/operator/map'; +import { Injectable } from '@angular/core'; +import { Router, Event, RoutesRecognized, NavigationStart } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class LoadingService { + loading$: Observable; + + constructor(private router: Router) { + this.loading$ = this.router.events.map((event: Event) => { + if ( event instanceof NavigationStart || event instanceof RoutesRecognized) { + return true; + } else { + // return false for NavigationEnd, NavigationError and NavigationCancel events + return false; + } + }); + } +} diff --git a/public/docs/_examples/rxjs/ts/app/main.ts b/public/docs/_examples/rxjs/ts/app/main.ts new file mode 100644 index 0000000000..961a226688 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/app/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/public/docs/_examples/rxjs/ts/example-config.json b/public/docs/_examples/rxjs/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/rxjs/ts/index.html b/public/docs/_examples/rxjs/ts/index.html new file mode 100644 index 0000000000..b69d174ea2 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/index.html @@ -0,0 +1,31 @@ + + + + + + + RxJS Observables + + + + + + + + + + + + + + + + + loading... + + + + diff --git a/public/docs/_examples/rxjs/ts/plnkr.json b/public/docs/_examples/rxjs/ts/plnkr.json new file mode 100644 index 0000000000..d85bc59fe3 --- /dev/null +++ b/public/docs/_examples/rxjs/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "RxJS", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0-9].*" + ], + "tags": ["rxjs", "observable"] +} diff --git a/public/docs/ts/latest/guide/rxjs.jade b/public/docs/ts/latest/guide/rxjs.jade new file mode 100644 index 0000000000..a89bf8c140 --- /dev/null +++ b/public/docs/ts/latest/guide/rxjs.jade @@ -0,0 +1,343 @@ +block includes + include ../_util-fns + +:marked + **Observables** provided by the Reactive Extensions for Javascript (RxJS) library provide applications with an extensive API + for handling asynchronous and event-based values produced over time. + + An application is made up of many different streams of information. Whether it be user input into + a form, navigating from one route another, making an HTTP request to fetch some data, updating the application view with + new data as its received, or many other examples, each of these events happen over time. Observables provide a interface to + handle the many different sources of events and help to transform these events as they flow throughout an application. + + This guide will serve as an introductory chapter to Observables, common uses cases for Observables in an Angular application + and how Observables are used and provided by the Angular framework. + + ## Table of Contents + * [The Observable](#definition "") + * [Observables and Promises](#promises "") + * [Observable Lifecycle](#lifecycle "") + * [Observer](#lifecycle "") + * [Subscriptions](#subscriptions "") + * [Creating Observables](#creating "") + * [Operators](#transforming "") + * [Error Handling](#error-handling "") + * [Framework APIs](#apis "") + * [Impact on Change Detection](#change-detection "") + * [Further Reading](#reading "") + +:marked + The Observable: a function at its core + + 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 + returns a function for cancellation. It represents an action that can be performed. This action may be performed right now, or at some point + in the future. + + 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 + and returns the new value. + + // Example of simple counter + + The same functionality can be produced with an Observable. Observables don't return values directly, as they can be produced synchronously asynchronously. + An Observer is used by an Observable to consume its produced values. + + // Example of observable counter + + 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. + 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. + It represents an action we've defined, but it hasn't been executed. + + // Example of subscribing to observable counter + + Observable streams are **cold** or **lazy** by nature, meaning that until you invoke them, they will not produce + any values. Invoking an Observable is done using the `subscribe` method, which makes the Observable **hot** and calls the observer's method to produce + values. The `subscribe` function also returns an object with an `unsubscribe` from an Observable and no longer receive the values it produces. + + // Example of unsubscribing + + Managing the lifecycle of an Observable will be discussed later in the chapter. + +:marked + Observables and Promises: More different than alike + + 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, + the `Promise` was the primary way of handling asynchronous events. Promises and Observables share some similarities as they both handle asynchronous events, + both implement a function to handle execution and error handling, but they are more different then alike. + + Promises + * Produce a one-time value + * Can be composed + * Are always resolved/rejected asynchronously + * Are always multicast to multiple receivers + + Observables + * Produce a number of values over time + * Can be composed + * Resolve synchronously/asynchronously + * Multicast when needed + + 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 + for easy cancellation, retrying upon failure and transformations. Observables include a rich library of operators, along with the extensibility to provide a more powerful + 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. + 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 + of an Observable. + +:marked + Observable Anatomy: Next, Error, and Complete + + 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 + lifetime: `next`, `error`, and `complete`. + + The `next` is called whenever the observable produces a new value, notifying the Observer + 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 + + + Let's modify the HeroesReadyComponent to produce two values when subscribed. + + // Example of Observable that produces 2 values + + When click the `increment` button this time and subscribes to the Observable, the counter will be incremented twice and produce two values. + + 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. + + // Example of Observable that errors + + The Observable notifies its Observer through that an error has occurred. + + 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, + a completion event can only happen once. + + // Example of Observable that produces values, then completes + +:marked + Observer: The Observable's Consumer + 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`, + `error`, and `complete`. + + An Observer is provided to an Observable through the `subscribe` method in 2 ways + + * As a single object containing 3 callbacks for each notification. + * As 3 arguments in the subscribe method for each notification + + // Example of single object and logging out each notification + + // Example of 3 arguments to subscribe method + +:marked + Subscription: Maintaining of resources + + 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. + 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 + finished with its execution. Each time an Observable is subscribed, it returns a `Subscription`. The `Subscription` is an object that handles the resources provided + by the Observable, along with the `unsubscribe`. The `unsubscribe` method provided by the `Subscription` disposes of the resources allocated by the Observable. + + // Example of unsubscribing + + 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 + 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 + memory leaks and other unwanted side effects. + + // Example of unsubscribe in ngOnDestroy + + In this example, only one subscription is used and disposed of. Managing multiple subscriptions using the `unsubscribe` method + has the potential to get unwieldy. You'll learn about different ways to to end subscriptions of multiple Observables later in the chapter. + + // Example of unsubscribe using a Subject + +:marked + Operators: Observable functions + + The Observable prototype contains many `operators`, which are methods that perform an operation. Operators are **pure functions** + that are stateless. Stateless operators are less error-prone as they are provided an Observable, in which an operation is performed on, + and return a new Observable, without modifying the original Observable. The Observable prototype comes with a minimal set of operators by default, + with a large set of operators that can be added. Operators are also used to (create Observable instances) from existing events. + + There are different ways to access the operators provided by RxJS: By patching the Observable prototype or by importing the operator functions directly. + 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 + few options below: + + // Example of importing entire rxjs/Rx library + + 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 + the available operators and adding the entire set increases the overall bundle size of your application. This is + only recommended for sample apps, and in testing environments. + + // Example of patching the Observable prototype + + Since minimizing bundle size is a recommended practice, you should only import the operators you need, where you need them. + This approach includes importing operators multiple times in different files, but safeguards against using operators + without having patched the Observable prototype first. Since feature areas can be loaded lazily, this also allows you the benefit + of keeping certain operators in separate bundles and only loaded them when needed. + + // Example of importing operators directly + + // Note about tree-shaking when pat + + NOTE: Operators patched onto the Observable prototype that are unused will not be tree-shaken. + + Operators can also be directly imported and used without patching the Observable prototype. This is the + recommended option for third-party libraries, staying with the approach to only import what you need. It + is also more suited for tree-shaking when bundling your Angular application. + +:marked + + * Pure functions + + Subscribe + Produce Values + Unsubscribe + Complete + + Observer + - Provided to an Observable + - Next, Error, Complete callbacks + - Next to produce values + - Error when errors occur + - Complete when observable completes + + Subscription + - Function to release resources allocated to Observable + - Returns object with unsubscribe to clean up + - ngOnDestroy vs takeUntil + + Creating Observables + - From events + - From a Promise + - From multiple observables + - Example: Create observable from input on blur event + + Subject + - Is an Observable + - Keeps an internal list of subscribers + - Multicasting to multiple receivers + - Observable unicast to one receiver for execution + - Example: Counter using a subject + + BehaviorSubject + - Is an Observable + - Keeps a memory of the current value + - Each subscriber gets the same value + - Example: EventAggregator Service + + Operators + - Stateless + - Pure + - Less error prone + - Chainable + - Fluent + - Import all vs Direct Import vs Observable patch + +:marked + RxJS in Angular Framework + + - Uses Observables internally + - Only uses what's needed + - Imports operators + - Exposes Reactive APIs + - Async Pipe + - Reactive Forms + - HTTP + - Router + - Example: Hero Search Typeahead + + +Goals + + * Provide simple definition of what an Observable is. + * Talk about the predecessor to Observables - The Promise + * Why Observables are more prevalent. + * Also support conversion to a promise. + +:marked + Creating Observables + cold/hot + subscribe - next/error/complete + unsubscribe + + * Observable + * Subject + * BehaviorSubject + +:marked + Handling Lifecycle + Subscription + takeUntil + +:marked + Operators: Transforming Observables + Import entire lib/Import where used/rxjs-extensions file + Observable patch prototype vs direct import + - chaining + - tree shaking + +:marked + Creation Operators + * fromEvent + * of + +:marked + Transforming Operators + * map + * (flat)mergeMap + * switchMap + * toPromise + * share + +:marked + Filtering operators + * distinctUntilChanged + * filter + * debounceTime + +:marked + Utility operators + * do + * delay + +:marked + Error Handling + + * catch + * retry + +:marked + Sharing data + + * Event Aggregator service + * share value between all subscribers + +:marked + Observables within Angular + + Only makes use of minimal set + +:marked + Framework APIs + * Outputs (Event Emitter) + * Forms (Reactive Forms) + * valueChanges + * distinctUntilChanged + * debounceTime + + * Async Pipe (Handles template subscriptions) + * switchMap + + * Router (Parameters, Events) + * loaded + + * Http (All request methods) + * toPromise + * catch + * retry + +:marked + Impact on Change Detection + + * Use with Inputs + * Change detection triggered + * OnPush ChangeDetectionStrategy + +:marked + Further Reading + + * Official Docs