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

Commit e2001b2

Browse files
brandonrobertswardbell
authored andcommitted
Added writeup for stream integration
1 parent b69e8d3 commit e2001b2

File tree

8 files changed

+130
-99
lines changed

8 files changed

+130
-99
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"><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>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// #docplaster
2+
// #docregion
3+
import { Component, OnInit } from '@angular/core';
4+
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
5+
6+
import { HeroService } from './hero.service';
7+
8+
@Component({
9+
moduleId: module.id,
10+
templateUrl: './add-hero.component.html',
11+
styles: [ '.error { color: red }' ]
12+
})
13+
export class AddHeroComponent implements OnInit {
14+
form: FormGroup;
15+
showErrors: boolean = false;
16+
success: boolean;
17+
18+
constructor(
19+
private formBuilder: FormBuilder,
20+
private heroService: HeroService
21+
) {}
22+
23+
ngOnInit() {
24+
this.form = this.formBuilder.group({
25+
name: ['', [Validators.required]]
26+
});
27+
}
28+
29+
save(model: any) {
30+
// TODO: Save hero
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// #docplaster
2+
// #docregion
3+
import 'rxjs/add/operator/takeUntil';
4+
import 'rxjs/add/observable/merge';
5+
import { Component, OnInit, OnDestroy } from '@angular/core';
6+
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
7+
8+
import { Hero } from './hero';
9+
import { HeroService } from './hero.service';
10+
import { Observable } from 'rxjs/Observable';
11+
import { Subject } from 'rxjs/Observable';
12+
13+
@Component({
14+
moduleId: module.id,
15+
templateUrl: './add-hero.component.html',
16+
styles: [ '.error { color: red }' ]
17+
})
18+
export class AddHeroComponent implements OnInit, OnDestroy {
19+
form: FormGroup;
20+
showErrors: boolean = false;
21+
success: boolean;
22+
23+
constructor(
24+
private formBuilder: FormBuilder,
25+
private heroService: HeroService
26+
) {}
27+
28+
ngOnInit() {
29+
this.form = this.formBuilder.group({
30+
name: ['', [Validators.required]]
31+
});
32+
}
33+
34+
save(model: Hero) {
35+
this.heroService.addHero(model.name)
36+
.subscribe(() => {
37+
this.success = true;
38+
});
39+
}
40+
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
<!-- docregion -->
12
<h2>ADD HERO</h2>
3+
24
<form [formGroup]="form" (ngSubmit)="save(form.value)" *ngIf="!success">
35
<p>
46
*Name: <input type="text" formControlName="name" #heroName><br>
@@ -7,7 +9,7 @@ <h2>ADD HERO</h2>
79
<span *ngIf="showErrors && form.hasError('taken', ['name'])" class="error">Hero name is already taken</span>
810
</p>
911
<p>
10-
<button type="submit" [disabled]="(form.statusChanges | async) === 'INVALID'">Save</button>
12+
<button type="submit" [disabled]="!form.valid">Save</button>
1113
</p>
1214
</form>
1315

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'rxjs/add/observable/merge';
66
import 'rxjs/add/operator/debounceTime';
77
import 'rxjs/add/operator/do';
88
import 'rxjs/add/operator/switchMap';
9-
import 'rxjs/add/operator/take';
9+
import 'rxjs/add/operator/takeUntil';
1010
import { Component, OnInit, OnDestroy, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
1111
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
1212
import { Observable } from 'rxjs/Observable';
@@ -24,9 +24,9 @@ export class AddHeroComponent implements OnInit, OnDestroy, AfterViewInit {
2424
@ViewChild('heroName', { read: ElementRef }) heroName: ElementRef;
2525

2626
form: FormGroup;
27-
onDestroy$ = new Subject();
2827
showErrors: boolean = false;
2928
success: boolean;
29+
onDestroy$ = new Subject();
3030

3131
constructor(
3232
private formBuilder: FormBuilder,

Diff for: public/docs/_examples/rxjs/ts/src/app/hero.service.4.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,6 @@ export class HeroService {
3838

3939
addHero(name: string): Observable<Response> {
4040
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);
41+
.post(this.heroesUrl, { name }, {headers: this.headers});
4942
}
5043
}

Diff for: public/docs/_examples/rxjs/ts/src/app/hero.service.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ export class HeroService {
3838

3939
addHero(name: string): Observable<Response> {
4040
return this.http
41-
.post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers});
41+
.post(this.heroesUrl, { name }, {headers: this.headers});
4242
}
4343

4444
isNameAvailable(name: string): Observable<boolean> {
4545
return this.http
46-
.get(`app/heroes/?name=${name}`)
46+
.get(`${this.heroesUrl}/?name=${name}`)
4747
.map(response => response.json().data)
4848
.map(heroes => !heroes.find((hero: Hero) => hero.name === name));
4949
}

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

+36-86
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ block includes
2121
* [Sharing Data](#sharing-data "")
2222
* [Error Handling](#error-handling "")
2323
* [Framework APIs](#framework-apis "")
24-
* [Stream Integration](#integration "")
24+
* [Stream Integration](#stream-integration "")
2525
* [Further Reading](#further-reading "")
2626

2727
h3#definition The Observable: a function at its core
@@ -31,6 +31,12 @@ h3#definition The Observable: a function at its core
3131
returns a function for cancellation. It represents an action that can be performed. This action may be performed right now, or at some point
3232
in the future. An action can be anything, from simply "return a constant", "make an HTTP request" or "navigate to another page".
3333

34+
Angular makes extensive use of Observables internally and externally through its APIs to provide you
35+
with built-in streams to use in your Angular application. These APIs also manage their provided Observables
36+
efficiently. This means you don't need to unsubscribe from these provided Observables as their subscriptions
37+
are managed locally and will be destroyed when your components are destroyed. Observable streams are made available through the HTTP client,
38+
reactive forms, the router and view querying APIs.
39+
3440
Using Observables starts with an understanding of the basic principles, in which there are a wealth of resources that cover these in-depth. You should
3541
become more familiar with these principles, as it will help you with how to use these concepts in your Angular application. Below are a few resources
3642
to guide you in the concepts of an Observable and reactivity in general.
@@ -293,93 +299,37 @@ h3#retry Retry Failed Observable
293299

294300
// TODO Diagram for retry sequence
295301
296-
h3#framework-apis Framework APIs: Angular-provided Observables
302+
h3#stream-integration Stream Integration
297303
: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
304-
with Reactive or Model-Driven Forms, making external requests using Http and route information with the Router.
305-
By using Observables underneath, these APIs provide a consistent way for you to use and become more comfortable
306-
with using Observables in your application to handle your streams of data that are produced over time.
307-
Another major advantage of the streams provided by Angular is that they are managed for you,
308-
so no need to unsubscribe to clean up subscriptions.
309-
310-
Http
311-
312-
Making external requests from our application is a very common action in every application. You make a request,
313-
handle the success or failure of that request and wait for the request to be made again. Sometimes it’s not as
314-
simple as that as you need the be able to retry, cancel or delay requests. Being efficient with requests to save
315-
on data transferred is vital when every byte counts. HTTP requests are not a one-off action as many elements of
316-
your application is driven by external data. The Http client in Angular is built on top of Observables which
317-
provide the ability to handle one single request or multiple requests seamlessly, along with retrying and
318-
cancellations of requests.
319-
320-
Example: Use hero service to make a request, make it fail to show retries, conditional retry.
321-
// +makeExcerpt('src/app/hero-list.component.1.ts', '')
322-
323-
// :marked
324-
Async Pipe
325-
326-
As we’ve talked about previously, Observables must be subscribed to in order to handle the data they produce,
327-
and must be unsubscribed from in order to clean up the resources they have used. You’ve gone through how to
328-
subscribe to an Observable, get its data and provide that data through a variable in your class. There is also
329-
a built-in pipe available for use with template syntax to manage Observable subscriptions called the Async Pipe.
330-
When used in a template, the async pipe will subscribe to the Observable or evaluate a Promise, receive its
331-
emitted values and dispose of its subscription once the component is destroyed. The pipe also ties into Change Detection,
332-
so that when new values are produced, the component is marked for detection in order to determine whether it’s changes
333-
need to be reflected in your user interface. This is useful as it reduces the amount of boilerplate you need when setting
334-
up data to be fetched and provided to your template. We’ll learn later about cases where using an async pipe is beneficial
335-
versus managing your own subscription manually.
336-
337-
Example: Fetching heroes using a service, subscribing/unsubscribing manually then removing the subscription and delegating responsibility to the async pipe. Another example would be showing hero details with multiple async pipes, but instead using a single subscription.
338-
// +makeExcerpt('src/app/hero-detail.component.ts', '')
339-
340-
// :marked
341-
Forms
342-
With many applications, user input is required to initiate or complete an action. Whether it be logging in or filling out
343-
an information page, user data is another stream that needs to be handled. In contrast to template-driven forms where the
344-
responsibility is on the developer to gather the pieces of data from the form, the model-driven/Reactive forms uses
345-
Observables to easily provide a continuous stream of user input. By using the reactive approach, our form will be ready
346-
to handle user input streams from form fields, as well as provide that form data seamlessly to another stream for
347-
processing.
348-
349-
Example: Simple form that displays form status/value changes over time. Also displays creating an Observable from an existing event. valueChanges on individual field/entire form
350-
351-
Router
352-
353-
The browser URL is another stream of information. It provides you with a canonical link to the application view you are
354-
displaying at any given moment, along with information about what data to display. The Angular Router uses the browser URL
355-
to provide you with multiple streams that hook into the navigation process, URL data provided through your route
356-
configuration, parameters provided for context and path information. These pieces of data are provided by the Router
357-
through Observables, since we are certain that these streams happen in a continuous fashion as a user navigates throughout
358-
your Angular application.
359-
360-
Router Observables: Events, Parameters, Data, URL Segments
361-
362-
Example: Use router events to build a small loading service and component.
363-
// +makeExcerpt('src/app/loading.service.ts', '')
364-
// +makeExcerpt('src/app/loading.component.ts', '')
365-
366-
// h3#integration Stream Integration
367-
// :marked
368-
The Observables provided by these areas can be used together. The same set of functionality and extensibility can be combined together
369-
in a very powerful and practical way. A prime example is a hero search component that implements a typeahead search feature.
370-
Let’s start by gathering some requirements about what your typeahead will need.
371-
372-
* Take input from the user
373-
* Make a search based on that input
374-
* Only search when the user has changed the search terms
375-
* Cancel any in-progress requests if you user initiates a new search
376-
* Only make a request after the user hasn’t interacted within a certain time frame
377-
* Display search results
378-
* Sync the user’s search terms in the browser URL
379-
380-
Example: Hero search typeahead component
381-
// +makeExcerpt('src/app/hero-search.component.ts', '')
304+
Knowing Angular provides multiple Observables through different APIs is good, but putting those
305+
streams together in a valuable way is what you will be striving for. With a consistent interface
306+
provided by Observables, its easy to combine streams together. Let's look at building
307+
a form to add a Hero to your existing list. When adding a Hero, you'll want to check to see if the hero
308+
name is available before adding the hero, as well as checking validation while the form is being filled out.
309+
These definitely sound like streams of data we can tap into.
310+
311+
Let's start by adding a hero form component that uses a `Reactive Form`. You'll begin with a simple
312+
form template to enter and save the new hero.
313+
314+
+makeExcerpt('src/app/add-hero.component.1.ts (hero form component)', '')
315+
316+
:marked
317+
The hero form template is just getting started also.
318+
319+
+makeExcerpt('src/app/add-hero.component.1.html (hero form template)', '')
320+
321+
:marked
322+
You'll need to add a new method to the `HeroService` for adding a new hero. As mentioned earlier, the HTTP client returns an Observable
323+
`Response` that you can use the process the request and operate on if needed. You'll add this request to the `AddHeroComponent` when
324+
ready to save the hero data.
325+
326+
+makeExcerpt('src/app/hero.service.4.ts (add hero)', '')
382327

328+
: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.
330+
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
383333
h3#further-reading Further Reading
384334
:marked
385335
TODO

0 commit comments

Comments
 (0)