Upgrade to Pro — share decks privately, control downloads, hide ads and more …

angular form presentation

Avatar for Jiyoon Koo Jiyoon Koo
February 13, 2018
7

angular form presentation

Avatar for Jiyoon Koo

Jiyoon Koo

February 13, 2018
Tweet

Transcript

  1. What are forms? Take many different types of user input

    Allow user interaction Enforce validation before data can even be submitted 2 types of forms in Angular ‐ Template and Reactive 2
  2. Template‐driven forms ﴾steps 1/4﴿ Import Angular's FormsModule into your @NgModule

    This activates Angular Form API. import { NgModule } from '@angular/core'; @NgModule({ imports: [ ... FormsModule ] ... }) class AppModule {} Let's look at a very simple form <form> <label>Your name:</label> <input type="text"> <button type="submit">Submit</button> </form> 3
  3. Template‐driven forms ﴾steps, cont'd 2/4﴿ Angular's ngForm is already 'hooked

    into' <form> selector! To access this, we simply have to declare a local form variable as ngForm <form #form="ngForm"> <label>What's your name?</label> <input type="text"> <button type="submit">Submit</button> </form> This allows access to... A JSON representation of the form value Validity state of the entire form ﴾eg. ..controls.isDirty, ..controls.hasError , etc﴿ Delegates submission events 4
  4. Template‐driven forms ﴾steps, cont'd 3/4﴿ How to submit? Here's the

    typical way... <form #form="ngForm" (submit)="submitMyStuff(form.value)"> <label>What's your name?</label> <input type="text"> <button type="submit">Submit</button> </form> : Using (submit) will cause page reload when error is thrown! Instead, use (ngSubmit) to prevent this behavior. <form #form="ngForm" (ngSubmit)="submitMyStuff(form.value)"> 5
  5. Template‐driven forms ﴾steps, cont'd 4/4﴿ Register form controls using ngModel

    directive name attribute <input name="firstname" ngModel> if you want to group multiple inputs together, use ngModelGroup like this: <div ngModelGroup="address"> <label>Firstname:</label> <input name="firstname" ngModel> <label>Lastname:</label> <input name="lastname" ngModel> </div> 6
  6. Built‐in Validation Angular comes equipped with following validation directives required,

    requiredTrue, email, minLength, maxLength, pattern Use it like this <input required minlength="3"> 7
  7. Accessing validation state of built‐in validators If a validation error

    exists, you can access it in a FormControl's errors property <input #firstname="ngModel" required minlength="3"> <!‐‐ IF minlength validation error state is "true" ‐‐> <p *ngIf="firstname.errors.minlength"> oops! </p> Hint: this can also be applied as a class, using [ngClass] ..! firstname.errors object might look like this: firstname.errors = { minlength: { actualLength: 2, requiredLength: 3 }, required: true } 8
  8. Other validation states ngForm , ngModelGroup , ngModel keeps record

    of the following states, which are boolean: Dirty / Pristine ﴾did user change value of control?﴿ Touched / Untouched ﴾did user focus on control and move away?﴿ Valid / Invalid ﴾did user input pass all validation ?﴿ Access them like this: <form #form="ngForm"> <input ngModel name="firstname" required> <div *ngIf="!firstname.valid && !firstname.pristine"> show this div if firstname field is either invalid or dirty </div> <button [disabled]="!form.valid">Save</button> </form> 9
  9. Custom Validators! A validator is a function It takes FormControl

    as an input ... and returns null ﴾successful validation﴿ ... or returns an error object ﴾contains validation error﴿ 10
  10. Custom Validator ﴾for template driven form﴿ example Steps: ng generate

    directive [YOUR_VALIDATOR_NAME] Write your validator function in the directive and reference it in providers attribute Import and declare the directive in your @NgModule declarations array import { YOUR_VALIDATOR_NAME } from '...'; @NgModule({ declarations: [ YOUR_VALIDATOR_NAME ... ], .. }) Use it in template like this <input type="email" name="email" ngModel validateEmail> See example directive on next page 11
  11. import { Directive } from '@angular/core'; import { FormControl }

    from '@angular/forms/src/model'; import { NG_VALIDATORS } from '@angular/forms'; const VALID_EMAIL = [SOME_REGEX_FOR_EMAIL_VALIDATION]; // 1. export the function by itself export function validateEmail(c: FormControl) { var valid = VALID_EMAIL.test(c.value); // 1a. if INVALID: return error message // 1b. if VALID: return null return (!valid) ? { isEmailValid: { value: false } } : null; } // 2. Reference the exported function in useValue attribute @Directive({ // Angular will look for input tags with both selector: '[validateEmail][ngModel]', providers: [{ provide: NG_VALIDATORS, useValue: validateEmail, // this is called multi provider // it allows multiple deps for a single token // NG_VALIDATORS is a built‐in multi provider multi: true }] }) export class EmailValidatorDirective { constructor() { } } 12
  12. Custom Async Validators! Used when you need validation, but you

    require some service ﴾or some external dependency﴿ to perform it Returns an Observable ﴾or Promise ﴿ Similar beginnings as writing custom sync validators, but has a couple big differences. Steps: ng generate directive [VALIDATOR_NAME] Write a function that returns a function﴾!﴿ Do a depedency injection in the constructor of the directive Implement AsyncValidator in the class and return the function above inside the validate(c: FormControl) method that's implemented. Import and declare the directive in your @NgModule declarations array Use it in the template by calling the selectors chosen in the directive See example in next page... 13
  13. import { ContactsService } from './contacts.service'; import { Directive, forwardRef

    } from '@angular/core'; import { NG_ASYNC_VALIDATORS, FormControl, AsyncValidator } from '@angular/forms'; import { map } from 'rxjs/operators'; // factory function: a function that returns a function // it requires injection of contactsService export function checkEmailAvail(ctSvc: ContactsService) { return (c: FormControl) => { return ctSvc.isEmailAvailable(c.value) /* ctSvc.isEmailAvailable() response body: { msg?: string, error?: string } if response body does NOT contain error ‐‐> return null, ELSE return { emailTaken: true } */ .pipe(map(response => !response.error ? null : { emailTaken: true })); }; } @Directive({ selector: '[emailAvailValidator][ngModel]', providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => EmailAvailValidatorDirective), multi: true }] }) export class EmailAvailValidatorDirective implements AsyncValidator { _validate: Function; // service injection in the constructor constructor(contactSvc: ContactsService) { this._validate = checkEmailAvail(contactSvc); } validate(c: FormControl) { return this._validate(c); } } 14
  14. Q&A about example in previous slide Q: What's that forwardRef()

    thing in the provider attribute of @Directive ? A: It's kind of like hoisting. We need to do this because we want to have a class injected that we created in the same file. Q: Is there an order of execution for different validators? A: Yes. Angular executes sync validators before async validators. 15
  15. Reactive forms Another way to implement forms in Angular. Preferred

    ‐‐ easier to test! Uses a few APIs in Angular FormControl, FormGroup, FormBuilder, FormArray 16
  16. Reactive forms ﴾steps﴿ Import ReactiveFormsModule into your @NgModule import {

    ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ ... ReactiveFormsModule ] ... }) export class ContactsModule {} Import FormControl for each input value you want to keep track of import { FormControl } from '@angular/forms'; @Component(...) export class ContactCreatorComponent { firstname = new FormControl(); } Bind to formControl in the template <input [formControl]="firstname"> 17
  17. Reactive forms ﴾steps, cont'd﴿ Q: What if you want to

    group several FormControls? A: Use FormGroup in the component import { FormControl, FormGroup } from '@angular/forms; @Component(...) export class ContactCreatorComponent { form = new FormGroup({ firstname: new FormControl(), lastname: new FormControl() }) } ...And call it in the template as formGroup for group and formControlName for "child" FormControl inputs <form [formGroup]="form"> <input formControlName="firstname"> <input formControlName="lastname"> </form> 18
  18. This is way too much typing. I am far too

    lazy to type all that out Let's use the FormBuilder API instead! 19
  19. Reactive forms using FormBuilder API Import FormBuilder and FormGroup Inject

    it in component constructor Declare component variable form as type of FormGroup Build your form using .group() ! NB: you can build inner groups as well, see example below import { FormBuilder, FormGroup } from '@angular/forms'; @Component(...) export class ContactsCreatorComponent implements OnInit { form : FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ firstname: '', lastname: '', email: '', address: this.fb.group( { street: '', zip: '', city: '', country: '' }) }); } } NB: Make sure the template [formGroup] and formControlName attribute matches what you are composing in the component! They still need to correlate to each other... 20
  20. Let's apply ﴾Sync﴿ validators to our Reactive Form! Import Validators

    into component Edit each FormControl to be an array, pass it in as the second argument. If you have multiple validators, compose it via an array If you are using a custom validator, then import the Validator function ﴾not the directive﴿ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { validateEmail } from '../email‐validator.directive'; @Component(...) export class ContactsCreatorComponent implements OnInit { form : FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ firstname: ['', Validators.required], email: ['', [ Validators.required, Validators.minlength(3), validateEmail ] ] }); } } 21
  21. Let's apply ﴾Async﴿ validators to our Reactive Form! Edit each

    FormControl to be an array, pass it in as the third argument. Again, import the Custom Async Validator function ﴾not the directive﴿ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { validateEmail } from '../email‐validator.directive'; @Component(...) export class ContactsCreatorComponent implements OnInit { form : FormGroup; constructor(private fb: FormBuilder, private contactsService: ContactsService) {} ngOnInit() { this.form = this.fb.group({ firstname: ['', Validators.required], email: ['', [ Validators.required, Validators.minlength(3), validateEmail ], checkEmailAvail(this.contactsService) ] }); } } 22
  22. What about dynamic Form Fields ﴾ FormGroup of unknown size﴿?

    Example: Multiple ﴾optional﴿ phone fields Requires the use of FormArray API ﴾which we can call from FormBuilder.array() ﴿ FormArray API exposes push(c: FormControl) and removeAt(index: Number) 23
  23. FormArray ﴾steps﴿ Assign desired field as FormBuilder.array﴾﴿ Use .push() and

    .removeAt() as desired export class ContactsCreatorComponent implements OnInit { form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ ... phone: this.fb.array(['']) }); } addPhoneField() { const phoneFieldFormArray = <FormArray>this.form.get('phone'); phoneFieldFormArray.push(new FormControl('')); } removePhoneField(index) { const phoneFieldFormArray = <FormArray>this.form.get('phone'); phoneFieldFormArray.removeAt(index); } } <div formArrayName="phone"> <div *ngFor="let phone of form.get('phone').controls; let i = index; let l = last;"> <mat‐input‐container> <input [formControlName]="i" matInput placeholder="Phone"> </mat‐input‐container> <button mat‐icon‐button type="button" *ngIf="i >= 1" (click)="removePhoneField(i)"><mat‐icon>highlight_off</mat‐icon> </button> <button mat‐icon‐button type="button" *ngIf="l && phone.value != '' && i <= 3" (click)="addPhoneField()"><mat‐icon>add_circle_outline</mat‐icon> </button> </div> </div> 24