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

Commit 9a1e30e

Browse files
committed
docs(reactive-forms): re-write save for simplicity; delete much pedantry
1 parent 4b2259a commit 9a1e30e

File tree

8 files changed

+90
-117
lines changed

8 files changed

+90
-117
lines changed

public/docs/_examples/reactive-forms/ts/app/hero-detail-5.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import { Hero, states } from './data-model';
1717
})
1818
// #docregion v5
1919
export class HeroDetailComponent5 implements OnChanges {
20+
// #docregion hero
2021
@Input() hero: Hero;
22+
// #enddocregion hero
2123

2224
heroForm: FormGroup;
2325
states = states;

public/docs/_examples/reactive-forms/ts/app/hero-detail-6.component.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* tslint:disable:component-class-suffix */
2+
// #docplaster
23
// #docregion imports
34
import { Component, Input, OnChanges } from '@angular/core';
45
import { FormBuilder, FormGroup } from '@angular/forms';
@@ -31,9 +32,25 @@ export class HeroDetailComponent6 implements OnChanges {
3132
// #enddocregion address-form-group
3233
}
3334

34-
// #docregion set-value-on-changes
35+
// #docregion ngOnChanges
3536
ngOnChanges() {
37+
this.heroForm.reset({
38+
name: this.hero.name,
39+
address: this.hero.addresses[0] || new Address()
40+
});
41+
}
42+
// #enddocregion ngOnChanges
43+
44+
/* First version of ngOnChanges
45+
// #docregion ngOnChanges-1
46+
ngOnChanges()
47+
// #enddocregion ngOnChanges-1
48+
*/
49+
ngOnChanges1() {
50+
// #docregion reset
3651
this.heroForm.reset();
52+
// #enddocregion reset
53+
// #docregion ngOnChanges-1
3754
// #docregion set-value
3855
this.heroForm.setValue({
3956
name: this.hero.name,
@@ -43,7 +60,6 @@ export class HeroDetailComponent6 implements OnChanges {
4360
});
4461
// #enddocregion set-value
4562
}
46-
// #enddocregion set-value-on-changes
47-
63+
// #enddocregion ngOnChanges-1
4864
}
4965

public/docs/_examples/reactive-forms/ts/app/hero-detail-7.component.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,9 @@ export class HeroDetailComponent7 implements OnChanges {
3838

3939
// #docregion onchanges
4040
ngOnChanges() {
41-
// #docregion reset-refactor
4241
this.heroForm.reset({
4342
name: this.hero.name
4443
});
45-
// #enddocregion reset-refactor
4644
this.setAddresses(this.hero.addresses);
4745
}
4846
// #enddocregion onchanges

public/docs/_examples/reactive-forms/ts/app/hero-detail.component.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
// tslint:disable:no-unused-variable
21
// #docplaster
32
// #docregion
4-
// #docregion imports
5-
import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
6-
import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms';
3+
import { Component, Input, OnChanges } from '@angular/core';
4+
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
5+
76
import { Address, Hero, states } from './data-model';
8-
// #enddocregion imports
7+
// #docregion import-service
8+
import { HeroService } from './hero.service';
9+
// #enddocregion import-service
910

1011
// #docregion metadata
1112
@Component({
@@ -16,9 +17,6 @@ import { Address, Hero, states } from './data-model';
1617
// #enddocregion metadata
1718
export class HeroDetailComponent implements OnChanges {
1819
@Input() hero: Hero;
19-
// #docregion save-emitter
20-
@Output() save = new EventEmitter<Hero>();
21-
// #enddocregion save-emitter
2220

2321
heroForm: FormGroup;
2422
// #docregion log-name-change
@@ -27,7 +25,10 @@ export class HeroDetailComponent implements OnChanges {
2725
states = states;
2826

2927
// #docregion ctor
30-
constructor(private fb: FormBuilder) {
28+
constructor(
29+
private fb: FormBuilder,
30+
private heroService: HeroService) {
31+
3132
this.createForm();
3233
this.logNameChange();
3334
}
@@ -41,8 +42,7 @@ export class HeroDetailComponent implements OnChanges {
4142
}
4243

4344
ngOnChanges() {
44-
this.heroForm.reset();
45-
this.heroForm.patchValue({
45+
this.heroForm.reset({
4646
name: this.hero.name
4747
});
4848
this.setAddresses(this.hero.addresses);
@@ -64,23 +64,23 @@ export class HeroDetailComponent implements OnChanges {
6464

6565
// #docregion on-submit
6666
onSubmit() {
67-
const saveHero: Hero = this.prepareSaveHero();
68-
this.save.emit(saveHero); // let parent decide what to do
69-
console.log(saveHero); // diagnostic
67+
this.hero = this.prepareSaveHero();
68+
this.heroService.updateHero(this.hero);
69+
this.ngOnChanges();
7070
}
7171
// #enddocregion on-submit
7272

7373
// #docregion prepare-save-hero
74-
prepareSaveHero() {
74+
prepareSaveHero(): Hero {
7575
const formModel = this.heroForm.value;
7676

7777
// deep copy of form model lairs
7878
const secretLairsDeepCopy: Address[] = formModel.secretLairs.map(
7979
(address: Address) => Object.assign({}, address)
8080
);
8181

82-
// `saveHero` contains only original hero value(s) or
83-
// deep copies of changed form model values
82+
// return new `Hero` object containing a combination of original hero value(s)
83+
// and deep copies of changed form model values
8484
const saveHero: Hero = {
8585
id: this.hero.id,
8686
name: formModel.name as string,
@@ -97,7 +97,7 @@ export class HeroDetailComponent implements OnChanges {
9797

9898
// #docregion log-name-change
9999
logNameChange() {
100-
const nameControl = this.heroForm.get('name') as FormControl;
100+
const nameControl = this.heroForm.get('name');
101101
nameControl.valueChanges.forEach(
102102
(value: string) => this.nameChangeLog.push(value)
103103
);

public/docs/_examples/reactive-forms/ts/app/hero-list.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ <h3 *ngIf="heroes">Select a hero:</h3>
1212
<h2>Hero Detail</h2>
1313
<h3>Editing: {{selectedHero.name}}</h3>
1414
<!-- #docregion hero-binding -->
15-
<hero-detail [hero]="selectedHero" (save)="onSave($event)"></hero-detail>
15+
<hero-detail [hero]="selectedHero"></hero-detail>
1616
<!-- #enddocregion hero-binding -->
1717
</div>

public/docs/_examples/reactive-forms/ts/app/hero-list.component.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,4 @@ export class HeroListComponent implements OnInit {
2525
}
2626

2727
select(hero: Hero) { this.selectedHero = hero; }
28-
29-
onSave(hero: Hero) {
30-
this.heroService.updateHero(hero)
31-
.then(() => this.getHeroes())
32-
.then(() => {
33-
const newHero = this.heroes.find(h => h === hero);
34-
this.select(newHero);
35-
});
36-
}
3728
}

public/docs/_examples/reactive-forms/ts/app/hero.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export class HeroService {
1919
// simulate server latency with delay
2020
const ix = heroes.findIndex(h => h.id === hero.id);
2121
setTimeout(() => {
22-
heroes[ix] = hero;
22+
// Demo: mutate cached hero
23+
Object.assign(heroes[ix], hero);
2324
resolve(hero);
2425
}, 500);
2526
});

public/docs/ts/latest/guide/reactive-forms.jade

Lines changed: 48 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,9 @@ a#intro
5656
the form controls and pull user-changed values back out. The component can
5757
observe changes in form control state and react to those changes.
5858

59-
In keeping with the reactive paradigm, the component
60-
preserves the immutability of the _data model_,
61-
treating it as a pure source of original values.
62-
Rather than update the data model directly,
63-
the component extracts user changes and forwards them to an external component or service,
64-
which does something with them (such as saving them)
65-
and returns a new _data model_ to the component that reflects the updated model state.
66-
67-
Using reactive form directives does not require you to follow all reactive priniciples,
68-
but it does facilitate the reactive programming approach should you choose to use it.
59+
One advantage of working with form control objects directly is that value and validity updates
60+
are always synchronous and under your control.
61+
You won't encounter the timing issues that sometimes plague a template-driven form.
6962

7063
### _Template-driven_ forms
7164

@@ -542,55 +535,48 @@ a#set-data
542535

543536
Now you know _how_ to set the _form model_ values. But _when_ do you set them?
544537
The answer depends upon when the component gets the _data model_ values.
545-
546-
If the `HeroDetailComponent` is editing a _new_ hero, the data model consists entirely of default values
547-
that you could copy to the `FormControls` when you create them in the component's constructor.
548-
That's effectively what you did when you set each control to the empty string.
549-
550-
If the `HeroDetailComponent` is editing an _existing_ hero, you have to wait for that hero to arrive.
551-
The component _might_ have the ability to retrieve the hero from the server (via an injected data service)
552-
in which case you could set the form model in the [ngOnInit](lifecyle-hooks.html#oninit) lifecycle hook.
553-
The final version of the `HeroDetailComponent` in the [Tour of Heroes](../tutorial/toh-pt6.html) does it this way.
554538

555-
The `HeroDetailComponent` in this reactive forms sample works a little differently.
556-
It's the _detail_ view nested within a _master/detail_ `HeroListComponent` ([discussed below](#hero-list)).
557-
539+
The `HeroDetailComponent` in this reactive forms sample is nested within a _master/detail_ `HeroListComponent` ([discussed below](#hero-list)).
558540
The `HeroListComponent` displays hero names to the user.
559541
When the user clicks on a hero, the list component passes the selected hero into the `HeroDetailComponent`
560-
by **binding to the latter's `hero` input property**.
542+
by binding to its `hero` input property.
561543

562544
+makeExample('reactive-forms/ts/app/hero-list.component.1.html', '','hero-list.component.html (simplified)')(format=".")
563545

564546
:marked
565547
In this approach, the value of `hero` in the `HeroDetailComponent` changes
566548
every time the user selects a new hero.
567-
You can't call _setValue_ in `ngOnInit` or you'll miss the user's selections.
568-
Put the _setValue_ logic in the [ngOnChanges](lifecyle-hooks.html#onchanges)
569-
hook, which Angular calls whenever the input `hero` property changes.
549+
You should call _setValue_ in the [ngOnChanges](lifecyle-hooks.html#onchanges)
550+
hook, which Angular calls whenever the input `hero` property changes
551+
as the following steps demonstrate.
570552

571-
You should also _reset the form_ when the hero changes so that
572-
control values from the previous hero are cleared and
573-
status flags are restored to the _pristine_ state.
553+
First, import the `ngOnChanges` and `import` symbol in `hero-detail.component.ts`.
574554

575-
In order to use `ngOnChanges`, add it to the `hero-detail.component.ts`
576-
imports.
555+
+makeExample('reactive-forms/ts/app/hero-detail-5.component.ts', 'import-input','app/hero-detail.component.ts (core imports)')(format=".")
577556

578-
+makeExample('reactive-forms/ts/app/hero-detail-5.component.ts', 'imports','app/hero-detail.component.ts (excerpt)')(format=".")
557+
:marked
558+
Add the `hero` input property.
559+
+makeExample('reactive-forms/ts/app/hero-detail-5.component.ts', 'hero')(format=".")
579560

580561
:marked
581-
Here's what `ngOnChanges` should look like:
562+
Add the `ngOnChanges` method to the class as follows:
582563

583-
+makeExample('reactive-forms/ts/app/hero-detail-6.component.ts', 'set-value-on-changes','app/hero-detail.component.ts (excerpt)')(format=".")
564+
+makeExample('reactive-forms/ts/app/hero-detail-6.component.ts', 'ngOnChanges-1','app/hero-detail.component.ts (ngOnchanges)')(format=".")
584565

585566
:marked
586-
You could, however, reset the value of the *formControls* in the `reset()`
587-
method by refactoring.
588-
## Ward, are the benefits that now you can dispense with `setValue` (so less code
589-
## and you don't have to set everything)? In `hero-detail-7.component.ts`, we also have
590-
## `this.setAddresses(this.hero.addresses);` in `ngOnChanges`.
591-
## Do I need to include it and talk about it?
567+
### _reset_ the form flags
568+
569+
You should reset the form when the hero changes so that
570+
control values from the previous hero are cleared and
571+
status flags are restored to the _pristine_ state.
572+
You could call `reset` at the top of `ngOnChanges` like this.
573+
+makeExample('reactive-forms/ts/app/hero-detail-6.component.ts', 'reset')(format=".")
592574

593-
+makeExample('reactive-forms/ts/app/hero-detail-7.component.ts', 'reset-refactor','app/hero-detail.component.ts (excerpt)')(format=".")
575+
:marked
576+
The `reset` method has an optional `state` value so you can reset the flags _and_ the control values at the same.
577+
Internally, `reset` passes the argument to `setValue`.
578+
A little refactoring and `ngOnChanges` becomes this:
579+
+makeExample('reactive-forms/ts/app/hero-detail-6.component.ts', 'ngOnChanges', 'app/hero-detail.component.ts (ngOnchanges - revised)')(format=".")
594580

595581
a#hero-list
596582
:marked
@@ -611,12 +597,11 @@ figure.image-display
611597

612598
When the user clicks on a hero,
613599
the component sets its `selectedHero` property which
614-
is **bound to the `hero` input property** of the `HeroDetailComponent`.
600+
is bound to the `hero` input property of the `HeroDetailComponent`.
615601
The `HeroDetailComponent` detects the changed hero and re-sets its form
616602
with that hero's data values.
617603

618604
A "Refresh" button clears the hero list and the current selected hero before refetching the heroes.
619-
The `HeroListComponent` also has an `onSave` method that you'll wire up later in this guide.
620605

621606
The remaining `HeroListComponent` and `HeroService` implementation details are not relevant to understanding reactive forms.
622607
The techniques involved are covered elsewhere in the documentation, including the _Tour of Heroes_
@@ -684,7 +669,7 @@ a#form-array
684669
You need a method to populate (or repopulate) the _secretLairs_ with actual hero addresses whenever
685670
the parent `HeroListComponent` sets the `HeroListComponent.hero` input property to a new `Hero`.
686671

687-
The following method replaces the _secretLairs_ `FormArray` with a new `FormArray`,
672+
The following `setAddresses` method replaces the _secretLairs_ `FormArray` with a new `FormArray`,
688673
initialized by an array of hero address `FormGroups`.
689674
+makeExample('reactive-forms/ts/app/hero-detail-7.component.ts', 'set-addresses')(format=".")
690675

@@ -780,9 +765,9 @@ a#observe-control
780765
that raises a change event.
781766

782767
These are properties, such as `valueChanges`, that return an RxJS `Observable`.
783-
You don't need to know much about RxJS `Observable` to watch for a form control value change.
768+
You don't need to know much about RxJS `Observable` to monitor form control values.
784769

785-
Add the following method to watch for changes to the value of the _name_ `FormControl`.
770+
Add the following method to log changes to the value of the _name_ `FormControl`.
786771
+makeExample('reactive-forms/ts/app/hero-detail.component.ts', 'log-name-change','app/hero-detail.component.ts (logNameChange)')(format=".")
787772

788773
:marked
@@ -819,53 +804,33 @@ figure.image-display
819804

820805
:marked
821806
### Save
822-
In this sample application, the `HeroDetailComponent` cannot perform the save operation.
823-
The component delegates that chore to its parent `HeroListComponent`.
824-
825-
When the user clicks the _Save_ button, the `HeroDetailComponent` raises a save event on its `save` output property.
826-
The parent `HeroListComponent`, which binds to that property, responds to the event and processes the event payload.
827-
+makeExample('reactive-forms/ts/app/hero-list.component.html', 'hero-binding','app/hero-list.component.html (save event binding)')(format=".")
828-
:marked
829-
The save event payload _could_ have any shape but often it's in the shape of the data model and that is the case in this example.
830-
+makeExample('reactive-forms/ts/app/hero-detail.component.ts', 'save-emitter')(format=".")
831-
832-
:marked
833-
The `HeroDetailComponent` save method (`onSubmit`) constructs a `saveHero`
834-
and emits a save message, with `saveHero` as the payload, for the `HeroListComponent` to process.
835-
807+
In this sample application, when the user submits the form,
808+
the `HeroDetailComponent` will pass an instance of the hero _data model_
809+
to a save method on the injected `HeroService`.
836810
+makeExample('reactive-forms/ts/app/hero-detail.component.ts', 'on-submit','app/hero-detail.component.ts (onSubmit)')(format=".")
837811
:marked
838-
In the reactive paradigm, the _data model_ is immutable so the save method can't update the component's `hero`
839-
and send that as the save event payload.
840-
841-
Instead, it must prepare `saveHero` whose values derive from a combination of original hero values (the `hero.id`)
842-
and deep copies of the potentially-changed form model values.
843-
844-
## Ward, stop lecturing :D
812+
This original `hero` had the pre-save values. The user's changes are still in the _form model_.
813+
So you create a new `hero` from a combination of original hero values (the `hero.id`)
814+
and deep copies of the changed form model values, using the `prepareSaveHero` helper.
845815

846816
+makeExample('reactive-forms/ts/app/hero-detail.component.ts', 'prepare-save-hero','app/hero-detail.component.ts (prepareSaveHero)')(format=".")
847817

848-
:marked
849-
In the reactive paradigm, the form model and its object contents must not leak out of the component.
850-
The `saveHero` in the event payload must be completely disconnected from the form model.
851-
Ideally, it is as immutable as the component's source `hero`.
852-
853-
If you assign the `formModel.secretLairs` to `saveHero.addresses` (see line commented out),
854-
the addresses in the `saveHero.addresses` array will be the same objects
855-
as the lairs in the `formModel.secretLairs`.
856-
A user's subsequent changes to a lair street will mutate an address street in the `saveHero`.
857-
The `prepareSaveHero` method makes copies of the form model's `secretLairs` objects so that can't happen.
818+
.l-sub-section
819+
:marked
820+
**Address deep copy**
858821

859-
Keeping data immutable takes effort and vigilance.
860-
Consider using an immutable helper library such as
861-
<a href="https://facebook.github.io/immutable-js/" target="_blank" title="Immutable">Immutable.js</a>.
822+
Had you assigned the `formModel.secretLairs` to `saveHero.addresses` (see line commented out),
823+
the addresses in `saveHero.addresses` array would be the same objects
824+
as the lairs in the `formModel.secretLairs`.
825+
A user's subsequent changes to a lair street would mutate an address street in the `saveHero`.
826+
827+
The `prepareSaveHero` method makes copies of the form model's `secretLairs` objects so that can't happen.
862828

863829
:marked
864830
### Revert (cancel changes)
865831
The user cancels changes and reverts the form to the original state by pressing the _Revert_ button.
866832

867-
Reverting is easy. Simply re-execute the `ngOnChanges` method that built the _form model_ from the current `hero` _data model_.
868-
The immutability rule guarantees that the `hero` hasn't changed.
833+
Reverting is easy. Simply re-execute the `ngOnChanges` method that built the _form model_ from the original, unchanged `hero` _data model_.
869834
+makeExample('reactive-forms/ts/app/hero-detail.component.ts', 'revert','app/hero-detail.component.ts (revert)')(format=".")
870835

871836
:marked
@@ -894,7 +859,7 @@ figure.image-display
894859
- Inspecting `FormControl` properties.
895860
- Setting data with `patchValue` and `setValue`.
896861
- Adding groups dynamically with `FormArray`.
897-
- Watching for changes to the value of a `FormControl`.
862+
- Observing changes to the value of a `FormControl`.
898863
- Saving form changes.
899864

900865
a#source-code

0 commit comments

Comments
 (0)