Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
18 views

Pluralsight - Angular Reactive Forms

Uploaded by

Iulian Sirbu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views

Pluralsight - Angular Reactive Forms

Uploaded by

Iulian Sirbu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 190

-

-
-
Template-driven vs. Reactive Forms

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Template-
Reactive
driven
Module
Angular Form Building Blocks
Overview - FormGroup
- FormControl

Template Syntax for Forms


Template-driven Forms
Complex Scenarios
State
Value
Validity Visited
Changed

pristine valid touched

dirty errors untouched


Form Building Blocks

FormControl FormGroup
Form Model
- Retains form state
- Retains form value
- Retains child controls
• FormControls
• Nested FormGroups
Template-driven Forms
Template
- Form element
- Input element(s)
- Data binding
- Validation rules (attributes)
- Validation error messages
- Form model automatically generated

Component Class
- Properties for data binding (data model)
- Methods for form operations,
such as submit
Reactive Forms
Component Class
- Form model
- Validation rules
- Validation error messages
- Properties for managing data (data
model)
- Methods for form operations,
such as submit
Template
- Form element
- Input element(s)
- Binding to form model
Directives
Template-driven
(FormsModule)
FormGroup
• ngForm
• ngModel
• ngModelGroup

<form (ngSubmit)="save()">
</form>
Directives
Template-driven
(FormsModule)
FormGroup
• ngForm
• ngModel
• ngModelGroup

<form (ngSubmit)="save()"
#signupForm="ngForm">
</form>
Directives
Template-driven
(FormsModule)
FormGroup
• ngForm
• ngModel
• ngModelGroup

<form (ngSubmit)="save()"
#signupForm="ngForm">
<button type="submit"
[disabled]="!signupForm.valid">
Save
</button>
</form>
Directives
Template-driven
(FormsModule)
FormGroup
• ngForm
• ngModel
• ngModelGroup FormControl

<form (ngSubmit)="save()">
<input id="firstNameId" type="text"
[(ngModel)]="customer.firstName"
name="firstName"
#firstNameVar="ngModel"/>
</form>
Directives
Template-driven Reactive
(FormsModule) (ReactiveFormsModule)
• ngForm • formGroup
• ngModel • formControl
• ngModelGroup • formControlName
• formGroupName
• formArrayName
HTML Form
customer.component.html
<form>
<fieldset>
<div>
<label for="firstNameId">First Name</label>
<input id="firstNameId" type="text"
placeholder="First Name (required)"
required
minlength="3" />
</div>
...
<button type="submit">Save</button>
</fieldset>
</form>
Template-driven Form
customer.component.html
<form (ngSubmit)="save()">
<fieldset>
<div [ngClass]="{'has-error': firstNameVar.touched && !firstNameVar.valid }">
<label for="firstNameId">First Name</label>
<input id="firstNameId" type="text"
placeholder="First Name (required)"
required
minlength="3"
[(ngModel)]="customer.firstName"
name="firstName"
#firstNameVar="ngModel" />
<span *ngIf="firstNameVar.touched && firstNameVar.errors">
Please enter your first name.
</span>
</div>
...
<button type="submit">Save</button>
</fieldset>
</form>
Reactive Form
customer.component.html
<form (ngSubmit)="save()" [formGroup]="signupForm">
<fieldset>
<div [ngClass]="{'has-error': formError.firstName }">
<label for="firstNameId">First Name</label>
<input id="firstNameId" type="text"
placeholder="First Name (required)"
formControlName="firstName" />
<span *ngIf="formError.firstName">
{{formError.firstName}}
</span>
</div>
...
<button type="submit">Save</button>
</fieldset>
</form>
Demo

Template-driven Form
Complex Scenarios

Dynamically add input elements

Watch what the user types

Wait validation until typing stops

Different validation for different situations

Immutable data structures


Angular Forms

Template-driven Reactive
Generated form model Manually created form model
HTML validation Validation in the class
Two-way data binding No two-way data binding
Angular Form Building Blocks
Summary - FormGroup
- FormControl

Template Syntax for Forms


Template-driven Forms
Complex Scenarios
Building a Reactive Form

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Module
Overview The Component Class
The Angular Module
The Template
Using setValue and patchValue
Simplifying with FormBuilder
Template-driven vs. Reactive Forms
Form Model
- Root FormGroup
- FormControl for each input element
- Nested FormGroups as desired
- FormArrays
Creating a FormGroup
customer.component.ts
...
import { FormGroup } from '@angular/forms';

...
export class CustomerComponent implements OnInit {
customerForm: FormGroup;
customer: Customer = new Customer();

ngOnInit(): void {
this.customerForm = new FormGroup({ });
}
}
Creating FormControls
customer.component.ts
...
import { FormGroup, FormControl } from '@angular/forms';

...
export class CustomerComponent implements OnInit {
...
ngOnInit(): void {
this.customerForm = new FormGroup({
firstName: new FormControl(),
lastName: new FormControl(),
email: new FormControl(),
sendCatalog: new FormControl(true)});
}
}
Demo

Building the Form Model


AppComponent BrowserModule

Customer- Reactive -
Component FormsModule

Angular Module
Binding to the Form Model
Reactive Forms Directives

Reactive Forms

• formGroup
• formControl
• formControlName
• formGroupName
• formArrayName
formGroup
customer.component.html
<form (ngSubmit)="save()" [formGroup]="customerForm">
...
</form>
formControlName
customer.component.html
<form (ngSubmit)="save()" [formGroup]="customerForm">
<fieldset>
<div ... >
<label for="firstNameId">First Name</label>
<input id="firstNameId" type="text"
placeholder="First Name (required)"
formControlName="firstName" />
<span ... >
...
</span>
</div>
...
</fieldset>
</form>
Accessing the Form Model Properties
customerForm.controls.firstName.valid

customerForm.get('firstName').valid
firstName = new FormControl();

ngOnInit(): void {
this.customerForm = new FormGroup({
firstName: this.firstName,
...
});
}

firstName.valid
Using setValue and patchValue
this.customerForm.setValue({
firstName: 'Jack',
lastName: 'Harkness',
email: 'jack@torchwood.com'
});

this.customerForm.patchValue({
firstName: 'Jack',
lastName: 'Harkness'
});
FormBuilder

Creates a form model from a configuration


Shortens boilerplate code
Provided as a service
FormBuilder Steps

Import
FormBuilder

import { FormBuilder } from '@angular/forms';


FormBuilder Steps

Inject the
Import
FormBuilder
FormBuilder
instance

constructor(private fb: FormBuilder) { }


FormBuilder Steps

Inject the
Import Use the
FormBuilder
FormBuilder instance
instance

this.customerForm = this.fb.group({
firstName: null,
lastName: null,
email: null,
sendCatalog: true
});
FormBuilder's FormControl Syntax
this.customerForm = this.fb.group({
firstName: '',
sendCatalog: true
});

this.customerForm = this.fb.group({
firstName: {value: 'n/a', disabled: true},
sendCatalog: {value: true, disabled: false}
});

this.customerForm = this.fb.group({
firstName: [''],
sendCatalog: [{value: true, disabled: false}]
});
Checklist: Component Class
Create a property for the root FormGroup
Create the FormGroup instance
Pass in each FormControl instance

ngOnInit(): void {
this.customerForm = new FormGroup({
firstName: new FormControl(),
lastName: new FormControl(),
email: new FormControl(),
sendCatalog: new FormControl(true)
});
}
Checklist: FormBuilder
Import FormBuilder
Inject the FormBuilder instance
Use that instance

ngOnInit(): void {
this.customerForm = this.fb.group({
firstName: '',
lastName: '',
email: '',
sendCatalog: true
});
}
Checklist: Angular Module
Import ReactiveFormsModule
Add ReactiveFormsModule to the imports
array
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule
],
declarations: [
AppComponent,
CustomerComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
Checklist: Template
Bind the form element to the FormGroup
property
<form class="form-horizontal"
(ngSubmit)="save()"
[formGroup]="customerForm">

Bind each input element to its associated


FormControl
<input class="form-control"
id="firstNameId"
type="text"
placeholder="First Name (required)"
formControlName="firstName" />
Summary The Component Class
The Angular Module
The Template
Using setValue and patchValue
Simplifying with FormBuilder
Validation

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Module
Overview Setting Built-in Validation Rules
Adjusting Validation Rules at Runtime
Custom Validators
Custom Validators with Parameters
Cross-field Validation
Creating the Root FormGroup

ngOnInit(): void {
this.customerForm = this.fb.group({
firstName: '',
lastName: '',
email: '',
sendCatalog: true
});
}
Creating the FormControls
this.customerForm = this.fb.group({
firstName: '',
sendCatalog: true
});

this.customerForm = this.fb.group({
firstName: {value: 'n/a', disabled: true},
sendCatalog: {value: true, disabled: false}
});

this.customerForm = this.fb.group({
firstName: [''],
sendCatalog: [true]
});
Setting Built-in Validation Rules
this.customerForm = this.fb.group({
firstName: ['', Validators.required],
sendCatalog: true
});

this.customerForm = this.fb.group({
firstName: ['',
[Validators.required, Validators.minLength(3)]],
sendCatalog: true
});
Adjusting Validation Rules at Runtime
Adjusting Validation Rules at Runtime
myControl.setValidators(Validators.required);

myControl.setValidators([Validators.required,
Validators.maxLength(30)]);

myControl.clearValidators();

myControl.updateValueAndValidity();
Custom Validator

function myCustomValidator(c: AbstractControl):


{[key: string]: boolean} | null {
if (somethingIsWrong) {
return { 'myvalidator': true };
}
return null;
}
Custom Validator
Custom Validator

function myCustomValidator(c: AbstractControl):


{[key: string]: boolean} | null {
if (somethingIsWrong) {
return { 'myvalidator': true };
}
return null;
}
Custom Validator with Parameters

function myCustomValidator(param: any): ValidatorFn {


return (c: AbstractControl):
{[key: string]: boolean} | null => {
if (somethingIsWrong) {
return { 'myvalidator': true };
}
return null;
}
Cross-field Validation
Cross-field Validation: Nested FormGroup
this.customerForm = this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(3)]],
lastName: ['', [Validators.required, Validators.maxLength(50)]],
availability: this.fb.group({
start: ['', Validators.required],
end: ['', Validators.required]
})
});

<div formGroupName="availability">
...
<input formControlName="start"/>
...
<input formControlName="end"/>
</div>
Cross-field Validation
Cross-field Validation: Custom Validator
function dateCompare(c: AbstractControl):
{[key: string]: boolean} | null {
let startControl = c.get('start');
let endControl = c.get('end');
if (startControl.value !== endControl.value) {
return { 'match': true };
}
return null;
}

this.customerForm = this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(3)]],
lastName: ['', [Validators.required, Validators.maxLength(50)]],
availability: this.fb.group({
start: ['', Validators.required],
end: ['', Validators.required]
}, { validator: dateCompare })
});
Checklist: Setting Built-in Validation Rules
Import Validators
Pass in the validator or array of validators

ngOnInit(): void {
this.customerForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', [Validators.required,
Validators.maxLength(30)]],
email: '',
sendCatalog: true
});
}
Checklist: Adjusting Validation Rules
Determine when to make the change
Use setValidators or clearValidators
Call updateValueAndValidity

setNotification(notifyVia: string): void {


const p = this.myForm.get('phone');
if (notifyVia === 'text') {
p.setValidators(Validators.required);
} else {
p.clearValidators();
}
p.updateValueAndValidity();
}
Checklist: Custom Validators
Build a validator function
function myValidator(c: AbstractControl):
{[key: string]: boolean} | null {
if (somethingIsWrong) {
return { 'thisValidator': true };
}
return null;
}

Use it like any other validator


this.customerForm = this.fb.group({
firstName: ['', myValidator],
sendCatalog: true
});
Checklist: Custom Validators with Parameters
Wrap the validator function in a factory
function
function myValidator(param: any): ValidatorFn {
return (c: AbstractControl):
{[key: string]: boolean} | null => {
if (c.value === param) {
return { 'thisvalidator': true };
}
return null;
}}
Use it like any other validator
this.customerForm = this.fb.group({
firstName: ['', myValidator('test')]
});
Checklist: Cross-field Validation: FormGroup
Create a nested FormGroup
Add FormControls to that FormGroup
this.customerForm = this.fb.group({
name: ['', Validators.required],
availability: this.fb.group({
start: ['', Validators.required],
end: ['', Validators.required]
})
});
Update the HTML
<div formGroupName="availability">
<input formControlName="start"/>
<input formControlName="end"/>
</div>
Checklist: Cross-field Validation: Validator
Build a custom validator
function dateCompare(c: AbstractControl) {
if (c.get('start').value !==
c.get('end').value) {
return { 'match': true };
}
return null;
}

Use the validator


this.customerForm = this.fb.group({
name: ['', Validators.required],
availability: this.fb.group({
start: [null, Validators.required],
end: [null, Validators.required]
}, { validator: dateCompare })
});
Summary Setting Built-in Validation Rules
Adjusting Validation Rules at Runtime
Custom Validators
Custom Validators with Parameters
Cross-field Validation
Reacting to Changes

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Module
Overview
Watching
Reacting
Reactive Transformations
Watching

valueChanges property emits events on value changes

valueChanges is an Observable<any>

Observable is a collection of events that arrive asynchronously over


time

Subscribe to the observable to watch the events

statusChanges property emits events on validation changes


Watching
this.myFormControl.valueChanges.subscribe(value =>
console.log(value));

this.myFormGroup.valueChanges.subscribe(value =>
console.log(JSON.stringify(value)));

this.customerForm.valueChanges.subscribe(value =>
console.log(JSON.stringify(value)));
Reacting
Validation rules

Validation messages

User interface elements

Automatic suggestions

And more …
Demo

Displaying Validation Messages


Reactive Transformations

debounceTime

• Ignores events until a specific time has passed without


another event
• debounceTime(1000) waits for 1000 milliseconds (1 sec)
of no events before emitting another event

a b @ c

a
a b
a b @
a b @ c

Err Err Err


Reactive Transformations

debounceTime

• Ignores events until a specific time has passed without


another event
• debounceTime(1000) waits for 1000 milliseconds (1 sec)
of no events before emitting another event

a b @ c d e

1000 ms 1000 ms a
b
a @
b c
@ d
c e
Reactive Transformations

throttleTime

• Emits a value, then ignores subsequent values


for a specific amount of time

distinctUntilChanged

• Suppresses duplicate consecutive items

https://github.com/ReactiveX/rxjs/tree/master/src/operator
Checklist: Watching
Use the valueChanges Observable property
Subscribe to the Observable

this.myFormControl.valueChanges
.subscribe(value => console.log(value));
Checklist: Reacting
this.myFormControl.valueChanges
.subscribe(value =>
this.setNotification(value));

Change validation rules


Handle validation messages
Adjust user-interface elements
Provide automatic suggestions
And more
Checklist: Reactive Transformations
Add the operator
import 'rxjs/add/operator/debounceTime';

Use the operator


this.myFormControl.valueChanges
.debounceTime(1000)
.subscribe(value =>
console.log(value));
Summary
Watching
Reacting
Reactive Transformations
<div class="form-group"
[ngClass]="{'has-error': (emailVar.touched
|| emailVar.dirty) && !emailVar.valid }">
<label class="col-md-2 control-label"
for="emailId">Email</label>
<div class="col-md-8">
<input class="form-control" <div class="form-group"
id="emailId" type="email" [ngClass]="{'has-error': emailMessage}">
placeholder="Email (required)" <label class="col-md-2 control-label"
required for="emailId">Email</label>
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+"
[(ngModel)]="customer.email" <div class="col-md-8">
name="email" <input class="form-control"
#emailVar="ngModel" /> id="emailId" type="email"
<span class="help-block" placeholder="Email (required)"
*ngIf="(emailVar.touched formControlName = "email" />
|| emailVar.dirty) && emailVar.errors"> <span class="help-block"
<span *ngIf="emailVar.errors.required"> *ngIf="emailMessage">
Please enter your email address. {{ emailMessage }}
</span> </span>
<span *ngIf="emailVar.errors.pattern"> </div>
The confirmation does not match the </div>
email address.
</span>
</span>
</div>
</div>
Template-driven Reactive
Dynamically Duplicate Input Elements

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Module
Overview
Steps
Perform Each Step
- FormArrays
Steps to Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
Steps to Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
Steps to Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
FormGroup
FormGroup
FormControl FormControl

FormGroup
FormControl FormControl

FormGroup

FormControl

FormGroup
FormControl FormControl
Benefits of a FormGroup

Match the
Check Watch for
value of the
touched, dirty, changes and
form model to
and valid state react
the data model

Dynamically
Perform cross
duplicate the
field validation
group
Steps to Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
Creating a FormGroup in a Method
buildAddress(): FormGroup {
return this.fb.group({
addressType: 'home',
street1: '',
street2: '',
city: '',
state: '',
zip: ''
});
}

this.customerForm = this.fb.group({
...
addresses: this.buildAddress()
});
Steps to Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
FormArray
FormArray FormArray

FormControl FormGroup

FormControl FormControl FormControl

FormGroup FormGroup
FormControl FormControl FormControl FormControl

FormGroup FormGroup
FormControl FormControl FormControl FormControl

FormControl
Creating a FormArray

this.myArray = new FormArray([...]);

this.myArray = this.fb.array([...]);
Steps to Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
Looping Through a FormArray

<div formArrayName="addresses"
*ngFor="let address of addresses.controls; let i=index">

<div [formGroupName]="i">
...
</div>

</div>
Steps to Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
Duplicate the Input Elements
addAddress(): void {
this.addresses.push(this.buildAddress());
}

<button class="btn btn-primary"


type="button"
(click)="addAddress()">
Add Another Address
</button>
Checklist: Dynamically Duplicate Input Elements

Duplicate
the input
Loop element(s)
through the
Create a FormArray
FormArray
Refactor to
make copies
Define a
FormGroup,
Define the if needed
input
element(s)
to duplicate
Summary
Steps
Perform Each Step
- FormArrays
Reactive Form in Context

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Module
Overview Sample Application
Routing to the Form
Reading a Route Parameter
Setting a canDeactivate Guard
Refactoring to a Custom Validation Class
APM Sample Application Architecture
Welcome Product
Component Filter Pipe

App Product List Star


index.html
Component Component Component

Product Data Product Edit Product Detail


Service Component Component
ReactiveForms-
BrowserModule
Module

HttpModule RouterModule RouterModule CommonModule

AppModule ProductModule SharedModule

ProductList -
AppComponent ProductService StarComponent
Component

ProductDetail -
WelcomeComponent ProductDetailGuard CommonModule
Component

Imports
ProductEditGuard ProductFilterPipe FormsModule
Exports
Declarations
Providers ProductEdit-
Component
Bootstrap
Routing Steps

Place
result

Activate
routes

Configure
routes
Configuring Routes

[
{ path: 'products', component: ProductListComponent },
{ path: 'product/:id', component: ProductDetailComponent },
{ path: 'productEdit/:id', component: ProductEditComponent }
]
Route Guards

[
{ path: 'products', component: ProductListComponent },
{ path: 'product/:id',
canActivate: [ ProductDetailGuard],
component: ProductDetailComponent },
{ path: 'productEdit/:id',
canDeactivate: [ ProductEditGuard ],
component: ProductEditComponent }
]
Tying Routes to Actions
app.component.ts
...

@Component({
selector: 'pm-app',
template: `
<ul class='nav navbar-nav'>
<li><a [routerLink]="['/welcome']">Home</a></li>
<li><a [routerLink]="['/products']">Product List</a></li>
<li><a [routerLink]="['/productEdit', '0']">Add Product</a></li>
</ul>
`
})
Placing the Views
app.component.ts
...

@Component({
selector: 'pm-app',
template: `
<ul class='nav navbar-nav'>
<li><a [routerLink]="['/welcome']">Home</a></li>
<li><a [routerLink]="['/products']">Product List</a></li>
<li><a [routerLink]="['/productEdit', '0']">Add Product</a></li>
</ul>
<router-outlet></router-outlet>
`
})
Reading Parameters from a Route
{ path: 'productEdit/:id', component: ProductEditComponent }

import { ActivatedRoute } from '@angular/router';

constructor(private route: ActivatedRoute) {


let id = +this.route.snapshot.params['id'];
...
}

constructor(private route: ActivatedRoute) {


this.sub = this.route.params.subscribe(
params => {
let id = +params['id'];
...
}
);
}
Building a Guard
product-guard.service.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';

@Injectable()
export class ProductEditGuard
implements CanDeactivate<ProductEditComponent> {

canDeactivate(component: ProductEditComponent): boolean {


...
}
}
Custom Validator with Parameters

function range(min:number, max:number): ValidatorFn {


return (c: AbstractControl):
{[key: string]: boolean} | null => {
if (c.value < min || c.value > max) {
return { 'range': true };
}
return null;
}
Checklist: Reactive Form in Context
Add a route configuration
Add user interface element(s) to activate the
route
Read the route parameter
Set up a canDeactivate guard
Refactor validators to a custom validation
class for reuse
Summary Sample Application
Routing to the Form
Reading a Route Parameter
Setting a canDeactivate Guard
Refactoring to a Custom Validation Class
Create, Read, Update and Delete
(CRUD) Using HTTP

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Web
Web Browser Server

index.html index.html

JavaScript JavaScript

(http://mysite/api/products/5)
Web
Response Service

Data
DB
Web
Web Browser Server

index.html index.html

JavaScript JavaScript

(http://mysite/api/products/5)
Web
Data Service
Response

Data
DB
Module Prerequisites
@Injectable()
export class ProductService { } constructor(private http: Http) { }

... ...
import { ProductService } import { Observable }
from './product.service'; from 'rxjs/Observable';
import 'rxjs/add/operator/do';
@NgModule({ import 'rxjs/add/operator/catch';
imports: [ ... ], import 'rxjs/add/operator/throw';
providers: [ ProductService ] import 'rxjs/add/operator/map';
}) ...
export class ProductModule { }
getProducts(): Observable<IProduct[]> {
return this.http.get(this.baseUrl)
.map(this.extractData);
}
Module
Overview Data Access Service
Creating Data
Reading Data
Updating Data
Deleting Data
APM Sample Application Architecture
Welcome Product
Component Filter Pipe

App Product List Star


index.html
Component Component Component

Product Data Product Edit Product Detail


Service Component Component
Why Build a Data Access Service?

Separation of Concerns

Reusability

Data Sharing
Sending an HTTP Request

Request Request
(Get) (GET)

Product Data Web


Http Service
Service Response Response Server
Steps to Building a Data Access Service

Write the
code to
Import issue each
Observable Http request
Inject the and the
Angular observable
Create and Http Service operators
register the
Register the data access
Angular service
Http Service
Demo

Building a Data Access Service


Setting up the Backend Server

Build the
Select a Define the
server-side
technology API
code
Faking a Backend Server

Directly return
Use a JSON
hard-coded
file
data

Write our own Use angular-


code using in-memory-
MockBackend web-api
Populating the Form with Data
HTTP Get Request
product.service.ts
...
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class ProductService {
private baseUrl = 'www.myWebService.com/api/products';

constructor(private http: Http) { }

getProduct(id: number): Observable<IProduct> {


const url = `${this.baseUrl}/${id}`;
return this.http.get(url)
.map(this.extractData);
}
}
Calling the Data Access Service
product-edit.component.ts
...

constructor(private productService: ProductService) { }


...

getProduct(id: number): void {


this.productService.getProduct(id)
.subscribe(
(product: IProduct) => this.onProductRetrieved(product),
(error: any) => this.errorMessage = <any>error
);
}
Demo

Populating the Form with Data


Saving Edits
Saving Edits
'id': 5,
'productName':
'id': 5, 'Hammer',
'productCode':
'productName': 'TBX-0048',
'Hammer',
'releaseDate':
'productCode': 'May 21, 2016',
'TBX-0048',
'description':
'releaseDate': 'Curved
'May 21,claw steel hammer',
2016',
'price': 8.9,
'description': 'Small curved claw steel hammer',
'starRating':
'price': 8.9, 4.8,
'imageUrl': … ',
'starRating': 4.6,
'tags': ['tools',
'imageUrl': … ', 'hammer', 'construction']
'tags': ['tools', 'hammer', 'home maintenance']

let p = Object.assign({}, this.product,


this.productForm.value);
Post vs Put

POST (api/products) PUT (api/products/5)


Posts data for a resource or Puts data for a specific resource
set of resources with an Id
Used to: Used to:
• Create a new resource when the • Create a new resource when the
server assigns the Id client assigns the Id
• Update a set of resources • Update the resource with the Id
Not idempotent Idempotent
HTTP Put Request
product.service.ts
...

@Injectable()
export class ProductService {
private baseUrl = 'www.myWebService.com/api/products';
constructor(private http: Http) { }

updateProduct(product: IProduct): Observable<IProduct> {


let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });

const url = `${this.baseUrl}/${product.id}`;


return this.http.put(url, product, options)
.map(() => product);
}
}
Calling the Data Access Service
product-edit.component.ts

editProduct: void {
this.productService.updateProduct(p)
.subscribe(
() => this.onSaveComplete(),
(error: any) => this.errorMessage = <any>error
);
}
Demo

Saving Edits
Creating New Items
Initializing an Object
product.service.ts

initializeProduct(): IProduct {
return {
id: 0,
productName: null,
productCode: null,
tags: [''],
releaseDate: null,
price: null,
description: null,
starRating: null,
imageUrl: null
}
}
HTTP Post Request
product.service.ts
...

@Injectable()
export class ProductService {
private baseUrl = 'www.myWebService.com/api/products';
constructor(private http: Http) { }

createProduct(product: IProduct): Observable<IProduct> {


let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });

return this.http.post(this.baseUrl, product, options)


.map(this.extractData);
}
}
Demo

Creating New Items


Deleting an Existing Item
HTTP Delete Request
product.service.ts
...

@Injectable()
export class ProductService {
private baseUrl = 'www.myWebService.com/api/products';
constructor(private http: Http) { }

deleteProduct(id: number): Observable<Response> {


let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });

const url = `${this.baseUrl}/${id}`;


return this.http.delete(url, options);
}
}
Calling the Data Access Service
product-edit.component.ts

deleteProduct: void {
this.productService.deleteProduct(this.product.id)
.subscribe(
() => this.onSaveComplete(),
(error: any) => this.errorMessage = <any>error
);
}
Demo

Deleting an Existing Item


CRUD Checklist: Register the Http Service

app.module.ts
...
import { HttpModule }
from '@angular/http';

@NgModule({
Add HttpModule to the imports array of
imports: [ HttpModule ], one of the application's Angular Modules
...
})
export class AppModule { }
CRUD Checklist: Data Access Service
Import what we need

product.service.ts

...
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
CRUD Checklist: Data Access Service
Import what we need
Define a dependency for the http client
service
product.service.ts - Use a constructor parameter

...
@Injectable()
export class ProductService {

constructor(private http: Http) { }


}
CRUD Checklist: Data Access Service
Import what we need

product.service.ts Define a dependency for the http client


service
...
- Use a constructor parameter
createProduct ...
getProduct ... Create a method for each http request
updateProduct ...
deleteProduct ...
CRUD Checklist: Data Access Service
Import what we need
Define a dependency for the http client
product.service.ts service
... - Use a constructor parameter

const url = Create a method for each http request


`${this.baseUrl}/${id}`;
return this.http.get(url); Call the desired http method, such as get
- Pass in the Url
CRUD Checklist: Data Access Service
Import what we need
Define a dependency for the http client
product.service.ts service
... - Use a constructor parameter

const url = Create a method for each http request


`${this.baseUrl}/${id}`;
return this.http.get(url) Call the desired http method, such as get
.map(this.extractData);
- Pass in the Url

Map the Http response to a JSON object


CRUD Checklist: Data Access Service
Import what we need
Define a dependency for the http client
product.service.ts service
... - Use a constructor parameter

const url = Create a method for each http request


`${this.baseUrl}/${id}`;
return this.http.get(url) Call the desired http method, such as get
.map(this.extractData)
.catch(this.handleError); - Pass in the Url

Map the Http response to a JSON object


Add error handling
CRUD Checklist: Using the Service

product-edit.component.ts Inject the Data Access Service


...

constructor(private ps: ProductService) { }


CRUD Checklist: Using the Service

product-edit.component.ts
Inject the Data Access Service
...
Call the subscribe method of the returned
this.ps.getProduct(id)
.subscribe();
observable
CRUD Checklist: Using the Service

product-edit.component.ts
Inject the Data Access Service
...
Call the subscribe method of the returned
this.ps.getProduct(id)
.subscribe(
observable
(product: IProduct) =>
Provide a function to handle an emitted
this.onRetrieved(product)
); item
CRUD Checklist: Using the Service

product-edit.component.ts
Inject the Data Access Service
...
Call the subscribe method of the returned
this.ps.getProduct(id)
.subscribe(
observable
(product: IProduct) =>
Provide a function to handle an emitted
this.onRetrieved(product),
(error: any) => item
this.errorMessage = error
); Provide an error function to handle any
returned errors
Summary Data Access Service
Creating Data
Reading Data
Updating Data
Deleting Data
Final Words

Deborah Kurata
CONSULTANT | SPEAKER | AUTHOR | MVP | GDE

@deborahkurata | blogs.msmvps.com/deborahk/
Template-
Reactive
driven
Angular Forms

Template-driven Reactive
Easy to use More flexible ->
more complex scenarios
Similar to Angular 1
Immutable data model
Two-way data binding ->
Minimal component code Easier to perform an action
on a value change
Automatically tracks form and
input element state Reactive transformations ->
DebounceTime or DistinctUntilChanged
Easily add input elements dynamically
Easier unit testing
Template-driven

public firstName = 'Jack',


public lastName = 'Harkness',
public email = '',
public sendCatalog = false,

Reactive

public firstName = 'Jack',


public lastName = 'Harkness',
public email = '',
public sendCatalog = false,
Sending an HTTP Request

Request Request
(Get) (GET)

Product Data Web


Http Service
Service Response Response Server
Checklist

Steps and tips


Revisit as you build
Learning More

Beginner Angular Courses


- Angular 2: Getting Started
- Angular 2: First Look

Template-driven Forms Course


- Angular 2 Forms

Angular Documentation
- Angular.io
@deborahkurata

Template-
Reactive
driven

You might also like