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

Commit d6c0186

Browse files
Added async form validation example for framework apis
1 parent 0d58e09 commit d6c0186

12 files changed

+182
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<h2>ADD HERO</h2>
2+
<form [formGroup]="form" (ngSubmit)="save(form.value)" *ngIf="!success">
3+
<p>
4+
*Name: <input type="text" formControlName="name" #heroName><br>
5+
<span *ngIf="showErrors && form.hasError('required', ['name'])" class="error">Name is required</span>
6+
<span *ngIf="(form.get('name').statusChanges | async) === 'PENDING'" class="error">Checking if name is already taken</span>
7+
<span *ngIf="showErrors && form.hasError('taken', ['name'])" class="error">Hero name is already taken</span>
8+
</p>
9+
<p>
10+
<button type="submit" [disabled]="(form.statusChanges | async) === 'INVALID'">Save</button>
11+
</p>
12+
</form>
13+
14+
<span *ngIf="success">The hero has been added</span>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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/operator/debounceTime';
7+
import 'rxjs/add/operator/do';
8+
import 'rxjs/add/operator/switchMap';
9+
import 'rxjs/add/operator/take';
10+
import { Component, OnInit, OnDestroy, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
11+
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
12+
import { Observable } from 'rxjs/Observable';
13+
import { Subject } from 'rxjs/Subject';
14+
15+
import { EventAggregatorService } from './event-aggregator.service';
16+
import { HeroService } from './hero.service';
17+
18+
@Component({
19+
moduleId: module.id,
20+
templateUrl: 'add-hero.component.html',
21+
styles: [ '.error { color: red }' ]
22+
})
23+
export class AddHeroComponent implements OnInit, OnDestroy, AfterViewInit {
24+
@ViewChild('heroName', { read: ElementRef }) heroName: ElementRef;
25+
26+
form: FormGroup;
27+
onDestroy$ = new Subject();
28+
showErrors: boolean = false;
29+
success: boolean;
30+
31+
constructor(
32+
private formBuilder: FormBuilder,
33+
private heroService: HeroService,
34+
private eventService: EventAggregatorService
35+
) {}
36+
37+
ngOnInit() {
38+
this.form = this.formBuilder.group({
39+
name: ['', [Validators.required], [(control: FormControl) => {
40+
return this.checkHeroName(control.value);
41+
}]]
42+
});
43+
}
44+
45+
checkHeroName(name: string) {
46+
return Observable.of(name)
47+
.switchMap(heroName => this.heroService.isNameAvailable(heroName))
48+
.map(available => available ? null : { taken: true });
49+
}
50+
51+
ngAfterViewInit() {
52+
const controlBlur$ = Observable.fromEvent(this.heroName.nativeElement, 'blur');
53+
54+
Observable.merge(
55+
this.form.valueChanges,
56+
controlBlur$
57+
)
58+
.debounceTime(300)
59+
.takeUntil(this.onDestroy$)
60+
.subscribe(() => this.checkErrors());
61+
}
62+
63+
checkErrors() {
64+
if (!this.form.valid) {
65+
this.showErrors = true;
66+
}
67+
}
68+
69+
save(model: any) {
70+
this.heroService.addHero(model.name)
71+
.subscribe(() => {
72+
this.success = true;
73+
this.eventService.add({
74+
type: 'hero',
75+
message: 'Hero Added'
76+
});
77+
});
78+
}
79+
80+
ngOnDestroy() {
81+
this.onDestroy$.complete();
82+
}
83+
}

public/docs/_examples/rxjs/ts/src/app/app-routing.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { NgModule } from '@angular/core';
44
import { RouterModule, Routes } from '@angular/router';
55
import { HeroListComponent } from './hero-list.component';
66
import { HeroCounterComponent } from './hero-counter.component';
7+
import { AddHeroComponent } from './add-hero.component';
78

89
const appRoutes: Routes = [
10+
{ path: 'hero/add', component: AddHeroComponent },
911
{ path: 'hero/counter', component: HeroCounterComponent },
1012
{ path: 'heroes', component: HeroListComponent },
1113
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },

public/docs/_examples/rxjs/ts/src/app/app.component.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { EventAggregatorService } from './event-aggregator.service';
88
template: `
99
<h1 class="title">RxJS in Angular</h1>
1010
11+
<a routerLink="/hero/add">Add Hero</a><br>
1112
<a routerLink="/heroes">Heroes</a><br>
1213
<a routerLink="/hero/counter">Hero Counter</a><br>
1314

public/docs/_examples/rxjs/ts/src/app/app.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { HeroListComponent } from './hero-list.component';
1010
import { HeroCounterComponent } from './hero-counter.component';
1111
import { MessageLogComponent } from './message-log.component';
1212
import { LoadingComponent } from './loading.component';
13+
import { AddHeroComponent } from './add-hero.component';
1314

1415
import { LoadingService } from './loading.service';
1516
import { HeroService } from './hero.service';
@@ -35,7 +36,8 @@ import { InMemoryDataService } from './in-memory-data.service';
3536
HeroCounterComponent,
3637
HeroListComponent,
3738
MessageLogComponent,
38-
LoadingComponent
39+
LoadingComponent,
40+
AddHeroComponent
3941
],
4042
providers: [
4143
HeroService,

public/docs/_examples/rxjs/ts/src/app/hero-counter.component.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'rxjs/add/operator/takeUntil';
66
import { Component, OnInit, OnDestroy } from '@angular/core';
77
import { Observable } from 'rxjs/Observable';
88
import { Observer } from 'rxjs/Observer';
9-
import { Subscription } from 'rxjs/Subscription';
109

1110
// #docregion import-subject
1211
import { Subject } from 'rxjs/Subject';
@@ -24,7 +23,6 @@ import { Subject } from 'rxjs/Subject';
2423
export class HeroCounterComponent implements OnInit, OnDestroy {
2524
count: number = 0;
2625
counter$: Observable<number>;
27-
sub: Subscription;
2826

2927
// #docregion onDestroy-subject
3028
onDestroy$ = new Subject();
@@ -37,15 +35,15 @@ export class HeroCounterComponent implements OnInit, OnDestroy {
3735
}, 1000);
3836
});
3937

40-
let counter1Sub = this.counter$
38+
this.counter$
4139
.takeUntil(this.onDestroy$)
4240
.subscribe();
4341

44-
let counter2Sub = this.counter$
42+
this.counter$
4543
.takeUntil(this.onDestroy$)
4644
.subscribe();
4745

48-
let counter3Sub = this.counter$
46+
this.counter$
4947
.takeUntil(this.onDestroy$)
5048
.subscribe();
5149
}

public/docs/_examples/rxjs/ts/src/app/hero.service.1.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
// #docplaster
22
// #docregion
33
import 'rxjs/add/operator/map';
4-
import { Injectable } from '@angular/core';
5-
import { Http, Response, Headers } from '@angular/http';
6-
import { Observable } from 'rxjs/Observable';
4+
import { Injectable } from '@angular/core';
5+
import { Http } from '@angular/http';
6+
import { Observable } from 'rxjs/Observable';
77

88
import { Hero } from './hero';
99

1010
@Injectable()
1111
export class HeroService {
12-
private headers = new Headers({'Content-Type': 'application/json'});
1312
private heroesUrl = 'api/heroes';
1413

1514
constructor(

public/docs/_examples/rxjs/ts/src/app/hero.service.2.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
44
import 'rxjs/add/observable/of';
55
import 'rxjs/add/operator/catch';
66
import { Injectable } from '@angular/core';
7-
import { Http, Headers } from '@angular/http';
7+
import { Http } from '@angular/http';
88
import { Observable } from 'rxjs/Observable';
99

1010
import { Hero } from './hero';

public/docs/_examples/rxjs/ts/src/app/hero.service.3.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import 'rxjs/add/operator/catch';
66
// #docregion retry-import
77
import 'rxjs/add/operator/retry';
88
// #enddocregion retry-import
9-
import { Injectable } from '@angular/core';
10-
import { Http, Response, Headers } from '@angular/http';
11-
import { Observable } from 'rxjs/Observable';
9+
import { Injectable } from '@angular/core';
10+
import { Http } from '@angular/http';
11+
import { Observable } from 'rxjs/Observable';
1212

1313
import { Hero } from './hero';
1414

1515
@Injectable()
1616
export class HeroService {
17-
private headers = new Headers({'Content-Type': 'application/json'});
1817
private heroesUrl = 'api/heroes';
1918

2019
constructor(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/operator/map';
4+
import 'rxjs/add/observable/of';
5+
import 'rxjs/add/operator/catch';
6+
// #docregion retry-import
7+
import 'rxjs/add/operator/retry';
8+
// #enddocregion retry-import
9+
import { Injectable } from '@angular/core';
10+
import { Http, Response, Headers } from '@angular/http';
11+
import { Observable } from 'rxjs/Observable';
12+
13+
import { Hero } from './hero';
14+
15+
@Injectable()
16+
export class HeroService {
17+
private headers = new Headers({'Content-Type': 'application/json'});
18+
private heroesUrl = 'api/heroes';
19+
20+
constructor(
21+
private http: Http
22+
) {}
23+
24+
// #docregion getHeroes-failed
25+
getHeroes(fail?: boolean): Observable<Hero[]> {
26+
return this.http.get(`${this.heroesUrl}${fail ? '/failed' : ''}`)
27+
// #enddocregion getHeroes-failed
28+
.retry(3)
29+
.map(response => response.json().data as Hero[])
30+
// #enddocregion getHeroes-failed
31+
.catch((error: any) => {
32+
console.log(`An error occurred: ${error}`);
33+
34+
return Observable.of([]);
35+
});
36+
// #docregion getHeroes-failed
37+
}
38+
39+
addHero(name: string): Observable<Response> {
40+
return this.http
41+
.post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers});
42+
}
43+
44+
isNameAvailable(name: string): Observable<boolean> {
45+
return this.http
46+
.get(`api/heroes/?name=${name}`)
47+
.map(response => response.json().data)
48+
.map(heroes => heroes.length === 0);
49+
}
50+
}

public/docs/_examples/rxjs/ts/src/app/hero.service.ts

+12
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,16 @@ export class HeroService {
3535
});
3636
// #docregion getHeroes-failed
3737
}
38+
39+
addHero(name: string): Observable<Response> {
40+
return this.http
41+
.post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers});
42+
}
43+
44+
isNameAvailable(name: string): Observable<boolean> {
45+
return this.http
46+
.get(`app/heroes/?name=${name}`)
47+
.map(response => response.json().data)
48+
.map(heroes => !heroes.find((hero: Hero) => hero.name === name));
49+
}
3850
}

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,13 @@ h3#retry Retry Failed Observable
294294
// TODO Diagram for retry sequence
295295
296296
h3#framework-apis Framework APIs: Angular-provided Observables
297-
// :marked
298-
Angular makes use of Observables internally and externally through its APIs template syntax using the Async pipe, user input
297+
:marked
298+
Angular makes extensive use of Observables internally and externally through its APIs to provide you
299+
with built-in streams to use in your Angular application. Along with the `Async Pipe`, and the HTTP Client,
300+
observable streams are made available through Reactive Forms, the Router and View Querying APIs.
301+
302+
//:marked
303+
are provided template syntax using the Async pipe, user input
299304
with Reactive or Model-Driven Forms, making external requests using Http and route information with the Router.
300305
By using Observables underneath, these APIs provide a consistent way for you to use and become more comfortable
301306
with using Observables in your application to handle your streams of data that are produced over time.

0 commit comments

Comments
 (0)