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

Commit b023ab0

Browse files
brandonrobertswardbell
authored andcommitted
Added stream integration section
1 parent e2001b2 commit b023ab0

File tree

6 files changed

+182
-28
lines changed

6 files changed

+182
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!-- #docregion -->
2+
<h2>ADD HERO</h2>
3+
4+
<form [formGroup]="form" (ngSubmit)="save()" *ngIf="!success">
5+
<p>
6+
Name: <input type="text" formControlName="name" #heroName><br>
7+
<span *ngIf="showErrors && form.hasError('required', ['name'])" class="error">Name is required</span>
8+
</p>
9+
<p>
10+
<button type="submit" [disabled]="!form.valid">Save</button>
11+
</p>
12+
</form>
13+
14+
<span *ngIf="success">The hero has been added</span>

Diff for: public/docs/_examples/rxjs/ts/src/app/add-hero.component.2.ts

+37-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
// #docplaster
22
// #docregion
3+
// #docregion rxjs-imports-1
34
import 'rxjs/add/operator/takeUntil';
45
import 'rxjs/add/observable/merge';
5-
import { Component, OnInit, OnDestroy } from '@angular/core';
6+
import 'rxjs/add/observable/fromEvent';
7+
// #enddocregion rxjs-imports-1
8+
// #docregion viewchild-imports
9+
import { Component, OnInit, OnDestroy, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
10+
// #enddocregion viewchild-imports
611
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
712

813
import { Hero } from './hero';
914
import { HeroService } from './hero.service';
15+
16+
// #docregion rxjs-imports-2
1017
import { Observable } from 'rxjs/Observable';
11-
import { Subject } from 'rxjs/Observable';
18+
import { Subject } from 'rxjs/Subject';
19+
// #enddocregion rxjs-imports-2
1220

21+
// #docregion viewchild-heroName
1322
@Component({
1423
moduleId: module.id,
1524
templateUrl: './add-hero.component.html',
1625
styles: [ '.error { color: red }' ]
1726
})
18-
export class AddHeroComponent implements OnInit, OnDestroy {
27+
export class AddHeroComponent implements OnInit, OnDestroy, AfterViewInit {
28+
@ViewChild('heroName', { read: ElementRef }) heroName: ElementRef;
29+
// #enddocregion viewchild-heroName
30+
1931
form: FormGroup;
2032
showErrors: boolean = false;
2133
success: boolean;
34+
onDestroy$ = new Subject();
2235

2336
constructor(
2437
private formBuilder: FormBuilder,
@@ -30,6 +43,27 @@ export class AddHeroComponent implements OnInit, OnDestroy {
3043
name: ['', [Validators.required]]
3144
});
3245
}
46+
// #docregion observable-event
47+
ngAfterViewInit() {
48+
const controlBlur$: Observable<Event> = Observable.fromEvent(this.heroName.nativeElement, 'blur');
49+
50+
Observable.merge(
51+
controlBlur$
52+
)
53+
.takeUntil(this.onDestroy$)
54+
.subscribe(() => this.checkForm());
55+
}
56+
57+
checkForm() {
58+
if (!this.form.valid) {
59+
this.showErrors = true;
60+
}
61+
}
62+
// #enddocregion observable-event
63+
64+
ngOnDestroy() {
65+
this.onDestroy$.complete();
66+
}
3367

3468
save(model: Hero) {
3569
this.heroService.addHero(model.name)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// #docplaster
2+
// #docregion
3+
// #docregion rxjs-imports-1
4+
import 'rxjs/add/operator/takeUntil';
5+
import 'rxjs/add/observable/merge';
6+
import 'rxjs/add/observable/fromEvent';
7+
// #enddocregion rxjs-imports-1
8+
// #docregion viewchild-imports
9+
import { Component, OnInit, OnDestroy, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
10+
// #enddocregion viewchild-imports
11+
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
12+
13+
import { Hero } from './hero';
14+
import { HeroService } from './hero.service';
15+
16+
// #docregion rxjs-imports-2
17+
import { Observable } from 'rxjs/Observable';
18+
import { Subject } from 'rxjs/Subject';
19+
// #enddocregion rxjs-imports-2
20+
21+
// #docregion viewchild-heroName
22+
@Component({
23+
moduleId: module.id,
24+
templateUrl: './add-hero.component.html',
25+
styles: [ '.error { color: red }' ]
26+
})
27+
export class AddHeroComponent implements OnInit, OnDestroy, AfterViewInit {
28+
@ViewChild('heroName', { read: ElementRef }) heroName: ElementRef;
29+
// #enddocregion viewchild-heroName
30+
31+
form: FormGroup;
32+
showErrors: boolean = false;
33+
success: boolean;
34+
onDestroy$ = new Subject();
35+
36+
constructor(
37+
private formBuilder: FormBuilder,
38+
private heroService: HeroService
39+
) {}
40+
41+
ngOnInit() {
42+
this.form = this.formBuilder.group({
43+
name: ['', [Validators.required]]
44+
});
45+
}
46+
// #docregion value-changes
47+
ngAfterViewInit() {
48+
const controlBlur$: Observable<Event> = Observable.fromEvent(this.heroName.nativeElement, 'blur');
49+
50+
Observable.merge(
51+
controlBlur$,
52+
this.form.get('name').valueChanges
53+
)
54+
.takeUntil(this.onDestroy$)
55+
.subscribe(() => this.checkForm());
56+
}
57+
58+
checkForm() {
59+
if (!this.form.valid) {
60+
this.showErrors = true;
61+
}
62+
}
63+
// #enddocregion value-changes
64+
65+
ngOnDestroy() {
66+
this.onDestroy$.complete();
67+
}
68+
69+
save(model: Hero) {
70+
this.heroService.addHero(model.name)
71+
.subscribe(() => {
72+
this.success = true;
73+
});
74+
}
75+
}

Diff for: public/docs/_examples/rxjs/ts/src/app/add-hero.component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ export class AddHeroComponent implements OnInit, OnDestroy, AfterViewInit {
5757
)
5858
.debounceTime(300)
5959
.takeUntil(this.onDestroy$)
60-
.subscribe(() => this.checkErrors());
60+
.subscribe(() => this.checkForm());
6161
}
6262

63-
checkErrors() {
63+
checkForm() {
6464
if (!this.form.valid) {
6565
this.showErrors = true;
6666
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// #docplaster
22
// #docregion
33
// #docregion imports
4+
import 'rxjs/add/operator/scan';
45
import { Injectable } from '@angular/core';
56
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
67
// #enddocregion imports
@@ -14,25 +15,11 @@ export interface AppEvent {
1415

1516
@Injectable()
1617
export class EventAggregatorService {
17-
_events: AppEvent[] = [];
18-
events$: BehaviorSubject<AppEvent[]>;
19-
20-
constructor() {
21-
this._events = [];
22-
this.events$ = new BehaviorSubject(this._events);
23-
}
18+
_events$: BehaviorSubject<AppEvent[]> = new BehaviorSubject<AppEvent[]>([]);
19+
events$ = this._events$
20+
.scan((events, event) => events.concat(event), []);
2421

2522
add(event: AppEvent) {
26-
this._events.push(event);
27-
this.notify();
28-
}
29-
30-
clear() {
31-
this._events = [];
32-
this.notify();
33-
}
34-
35-
notify() {
36-
this.events$.next(this._events);
23+
this._events$.next([event]);
3724
}
3825
}

Diff for: public/docs/ts/latest/guide/rxjs.jade

+49-5
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ h3#stream-integration Stream Integration
314314
+makeExcerpt('src/app/add-hero.component.1.ts (hero form component)', '')
315315

316316
:marked
317-
The hero form template is just getting started also.
317+
And the hero form template.
318318

319319
+makeExcerpt('src/app/add-hero.component.1.html (hero form template)', '')
320320

@@ -326,10 +326,54 @@ h3#stream-integration Stream Integration
326326
+makeExcerpt('src/app/hero.service.4.ts (add hero)', '')
327327

328328
:marked
329-
If you look at the template closer, you'll see the `showErrors` boolean, which hides the error messages until you're ready to display them.
329+
If you look at the template, you'll see the `showErrors` boolean, which hides the error messages until you're ready to display them.
330330
A good form waits until the user has interacted with the fields before displaying any errors, and you'll want to follow that same rule. So
331-
how can you display errors once an interaction has happened? Reactive form controls provide an Observable stream of `valueChanges` whenever
332-
the form input changes and we can subscribe to that. Let's add
331+
how can you display errors once an interaction has happened? Interaction on the input can be as simple as entering the field
332+
and leaving the field, also known as the blur event. Observables can be created from existing events. You'll use the `fromEvent`
333+
operator to create an Observable from the existing `blur` event on the hero name input field.
334+
335+
In order to access the input field, you'll need to add a template reference to to the element. The `heroName` template reference will
336+
give us access to the input field in the component class. The updated template is as follows:
337+
338+
+makeExcerpt('src/app/add-hero.component.2.html (heroName template reference)', '')
339+
340+
:marked
341+
Now that you can access the template reference, you'll need to import the `ViewChild` decorator, the `ElementRef` type
342+
and the `AfterViewInit` lifecycle hook.
343+
344+
+makeExcerpt('src/app/add-hero.component.2.ts (ViewChild imports)', 'viewchild-imports')
345+
346+
:marked
347+
You'll use the `ViewChild` decorator to target the `heroName` template reference in the component assigned to
348+
the `ElementRef` type.
349+
350+
+makeExcerpt('src/app/add-hero.component.2.ts (ViewChild ElementRef)', 'viewchild-heroName')
351+
352+
:marked
353+
As usual, you'll need to import a few instance and static operators to create the Observable event. As
354+
previously mentioned, you'll use the `takeUntil` operator to clean up any Observable streams once the component
355+
is destroyed. In order to create an Observable from an element event, the `fromEvent` observable creation operator
356+
is needed. The `fromEvent` let's you create a stream from existing events emitted by elements. An additional operator
357+
is the `merge` creation operator, which combines multiple streams together
358+
359+
+makeExcerpt('src/app/add-hero.component.2.ts (rxjs imports)', 'rxjs-imports-1')
360+
361+
:marked
362+
In order to use the `ViewChild`, you'll need to implement the `AfterViewInit` interface and the `ngAfterViewInit`
363+
lifecycle hook. The `Observable.merge` let's you compose multiple observables and will emit when any of the source
364+
Observables emit a value without waiting for each one. You'll subscribe to the Observable and check the validity of
365+
the form in order to show errors. Now when the user triggers the `blur` event on the input the errors will be displayed.
366+
367+
+makeExcerpt('src/app/add-hero.component.2.ts (Observable fromEvent)', '')
368+
369+
:marked
370+
Since you can compose multiple streams using `Observable.merge`, you can easily add additional streams to trigger
371+
the validation check. Since reactive forms provide an Observable of `valueChanges`, you can listen for value changes
372+
from the `name` field and display errors once the user inputs some data also. You can easily access this through the reactive
373+
forms getter. Update the merged observables to include the name valueChanges.
374+
375+
+makeExcerpt('src/app/add-hero.component.3.ts (Observable valueChanges)', 'value-changes')
376+
333377
h3#further-reading Further Reading
334378
:marked
335-
TODO
379+
// TODO link some resources

0 commit comments

Comments
 (0)