Introduction to the Reactive Approach
- Form is created programmatically and synchronized with the DOM
Reactive : Setup
- In the app.module we need to comment out FormsModule and add ReactiveFormsModule
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; // import { FormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, // FormsModule, HttpModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
app.component.ts
import { NgForm, FormGroup } from '@angular/forms'; ... reactiveForm: FormGroup;
Reactive: Creating a form in code
- Out intention now is bind an HTML form to a Form object created programmatically.
app.component.html
<h2>Reactive forms</h2> <div class="row"> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <form> <div class="form-group"> <label for="candidate">Candidate</label> <input type="text" id="candidate" class="form-control"> </div> <div class="form-group"> <label for="haircolor">Hair color</label> <select id="haircolor" type="select" class="form-control"> <option value="blonde">Blonde</option> <option value="brown">Brown</option> </select> </div> <button class="btn btn-primary" type="submit" >Submit</button> </form> </div> </div>
app.component.ts
//Reactive approach reactiveForm: FormGroup; ngOnInit() { this.reactiveForm = new FormGroup({ 'candidate': new FormControl(null), 'haircolor': new FormControl('blonde') }); }
Reactive: Syncing HTML and form
- The first thing is tells Angular not to detect form tags and use my FormGroup created in Typescript. We use formGroup and formControlName built-in directives.
app.component.html
... <form [formGroup]="reactiveForm"> ... <input type="text" id="candidate" class="form-control" formControlName="candidate"> ... <select id="haircolor" type="select" class="form-control" formControlName="haircolor">
Reactive: Submitting the form
- Here we can use ngSubmit as in TD forms , but we dont need any local reference to the form. We already have reference to the form in the Form object created.
app.component.html
<form [formGroup]="reactiveForm" (ngSubmit)="onSubmitReactive()">
app.component.ts
onSubmitReactive() { console.log('submitted! reactive', this.reactiveForm); }
Reactive: Adding Validation
- We will use Validators class to pass static references to functions to execute on validation.
- We will use an array of Validators for making multiple validations.
app.component.ts
ngOnInit() { this.reactiveForm = new FormGroup({ 'candidate': new FormControl(null, Validators.required), 'email': new FormControl(null, [Validators.required,Validators.email]), 'haircolor': new FormControl('brown') }); }
Reactive: Getting access to controls
app.component.html
<span class="help-block" *ngIf="!reactiveForm.get('candidate').valid && reactiveForm.get('candidate').touched">Please enter a valid candidate</span>
Reactive: Grouping controls
app.component.ts
ngOnInit() { this.reactiveForm = new FormGroup({ 'userData': new FormGroup({ 'candidate':new FormControl(null, Validators.required), 'email':new FormControl(null, [Validators.required,Validators.email]) }), 'haircolor': new FormControl('brown') }); }
app.component.html
... <form [formGroup]="reactiveForm" (ngSubmit)="onSubmitReactive()"> <div formGroupName="userData"> <div class="form-group"> <label for="candidate">Candidate</label> <input type="text" id="candidate" class="form-control" formControlName="candidate"> <span class="help-block" *ngIf="!reactiveForm.get('userData.candidate').valid && reactiveForm.get('userData.candidate').touched">Please enter a valid candidate</span> </div> <div class="form-group"> <label for="email-reactive">Email</label> <input type="text" id="email-reactive" class="form-control" formControlName="email"> </div> </div>
Reactive: Array of Form controls (FormArray)
- We will add hobbies dynamically as an input array.
- We will use formArray directive in the HTML and FormArray in the typescript.
app.component.html
<div formArrayName="hobbies"> <h3>Your hobbies</h3> <button class="btn btn-primary" (click)="onAddHobby()">Add Hobby</button> <div class="form-group" *ngFor="let control of reactiveForm.get('hobbies').controls; let i = index"> <input type="text" class="form-control" [formControlName]="i"> </div> </div>
app.component.ts
ngOnInit() { this.reactiveForm = new FormGroup({ 'userData': new FormGroup({ 'candidate':new FormControl(null, Validators.required), 'email':new FormControl(null, [Validators.required,Validators.email]) }), 'hobbies': new FormArray([]), 'haircolor': new FormControl('brown') }); } onAddHobby() { const control = new FormControl(null); (<FormArray>this.reactiveForm.get('hobbies')).push(control); }
Reactive: Creating Custom validators
- We will check candidate against a list of forbidden user names.
app.component.ts
this.reactiveForm = new FormGroup({ 'userData': new FormGroup({ 'candidate':new FormControl(null, [Validators.required, this.forbiddenNameValidation.bind(this)]), 'email':new FormControl(null, [Validators.required,Validators.email]) }), 'hobbies': new FormArray([]), 'haircolor': new FormControl('brown') }); } ... forbiddenNameValidation(control:FormControl): {[s: string]: boolean} { if(this.forbiddenUsernames.indexOf(control.value) !== -1) { return {'nameIsForbidden': true}; } return null; //Valid }
NOTE: It is important return null on validator function as it is how Angular recognizes validation is ok.
NOTE: We need to make bind(this) in the validators array for the custom validation for tells Angular to get the correct reference of the form control.
Reactive: Using Error Codes
- We will customize error messages for custom validations.
- We will check errors property in the form.controls object.
app.component.html
<div class="form-group"> <label for="candidate">Candidate</label> <input type="text" id="candidate" class="form-control" formControlName="candidate"> <span class="help-block" *ngIf="!reactiveForm.get('userData.candidate').valid && reactiveForm.get('userData.candidate').touched"> <span class="help-block" *ngIf="reactiveForm.get('userData.candidate').errors['nameIsForbidden']"> This name is forbidden </span> <span class="help-block" *ngIf="reactiveForm.get('userData.candidate').errors['required']"> Name is required </span> </span> </div>
Reactive: Creating a Custom Async Validator
- Sometimes we want to reach out a server to make a validation
- So we need to make an async operation , in this case we can pass to the FormControl constructor a third parameter telling Async Validators.
- In the example we simulate an async operation with a timeout and check if email is test@test.com
... 'email':new FormControl(null, [Validators.required,Validators.email], this.forbiddenEmails) ... forbiddenEmails(control:FormControl): Promise<any> | Observable<any> { const promise = new Promise<any>((resolve, reject) => { setTimeout(()=> { if(control.value === 'test@test.com') { resolve({'emailIsForbidden':true}); } else { resolve(null); } },1500); }); return promise; }
Reactive: Reacting to Status or Value Changes
- We can monitor all the values and status changing in our form in every moment with two observables of the reactive forms: valueChanges and statusChanges.
- We can hook on them:
ngOnInit() { ... this.reactiveForm.valueChanges.subscribe( (value) => {console.log(value);} ); this.reactiveForm.statusChanges.subscribe( (value) => {console.log(value);} ); }
Reactive: Setting and Patching values
- As in TD forms we can use setValue, patchValue and reset methods on the reactive form object.
Notes
Shortcut for property binding for strings
- Usually property binding use single quotes:
[pattern]="'*(0-9)'"
- When we re using a string in a property binding we can type a simpler syntax:
pattern="*(0-9)"
Patterns in template driven forms
- We can add pattern matching in template driven forms with the HTML5 pattern.
- For example we want numbers above 0 in this input field:
<input type="text" id="amount" class="form-control" name="amount" ngModel required pattern="^[1-9]+[0-9]*$">
ds