puddlejumper26 / blogs

Personal Tech Blogs
4 stars 1 forks source link

Angular Form - Reactive Form #59

Open puddlejumper26 opened 4 years ago

puddlejumper26 commented 4 years ago

Related articles

Angular Form - Template Driven Form Form Validation Comparisons between Reactive Forms and Template Driven Forms


Guidelines

Angular forms work in two different ways, either as Template Driven Forms or as Reactive Forms - also sometimes called Model-Driven Forms

1.0 What Are Angular Reactive Forms?

Reactive forms are also known as model-driven forms. This means that the HTML content changes depending on the code in the component. [2]

Demo 1 [1] Demo 2 for comparisons

Reactive forms are built around observable streams, where form inputs and values are provided as streams of input values, which can be accessed synchronously.

Reactive forms also provide a straightforward path to testing because you are assured that your data is consistent and predictable when requested.[1]

2.0 Differences Between Template-Driven and Reactive Forms [2][5]

Template-driven forms Reactive forms
Module FormsModule ReactiveFormsModule
Sync/Predictability Asynchronous Synchronous
Interactions/structures in template .html in component .ts
Form validation Directives Functions
Mutability immutability with observable operators Mutable

2.1 Common foundation of Reactive Form and Template-Driven Form[5]

Building Blocks Contents
FormControl tracks the value and validation status of an individual form control
FormGroup tracks the same values and status for a collection of form controls.
FormArray tracks the same values and status for an array of FormControl, FormGroup or FormArray instances.
ControlValueAccessor creates a bridge between Angular FormControl instances and native DOM elements.

2.2 Advantages and Disadvantages of Reactive Forms [2]

Features Ad\Dis Reasons
Advantages Easier to write Unit Tests all the form code and functionality is contained in the component
Disadvantages

3.0 Application process (Taken from Demo 1)

3.1 Registering the ReactiveFormsModule in the module.ts

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

@NgModule({
  imports: [
    // other imports ...
    ReactiveFormsModule
  ],
})
export class AppModule { }

3.2 A new FormControl in the component.ts

export class NameEditorComponent {
  name = new FormControl('');
}

3.3 Registering the control in the template .html

<label>
  Name:
  <input type="text" [formControl]="name">
</label>

3.4 Managing Control value

3.4.1 Displaying a form control value

<p>
  Value: {{ name.value }}
</p>

3.4.2 Replacing a form control in value component.ts

A form control instance provides a setValue() method that updates the value of the form control and validates the structure of the value provided against the control's structure.

When using the setValue() method with a form group or form array instance, the value needs to match the structure of the group or array.

updateName() {
  this.name.setValue('Nancy');
}

3.5 Grouping form controls

3.5.1 Creating a FormGroup instance in component.ts

import { FormGroup, FormControl } from '@angular/forms';

export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
}

3.5.2 Associating the FormGroup model and view in .html

<form [formGroup]="profileForm">

  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>

  <label>
    Last Name:
    <input type="text" formControlName="lastName">
  </label>

</form>

3.6 Creating nested form groups

Using a nested form group instance allows you to break large forms groups into smaller, more manageable ones

3.6.1 Creating a nested group

export class ProfileEditorComponent {
  profileForm = new FormGroup({           <==== 1st layer of form group
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({             <==== nested form group
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });
}

3.6.2 Grouping the nested form in the template

<form [formGroup]="profileForm">

  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>

  <label>
    Last Name:
    <input type="text" formControlName="lastName">
  </label>

<div formGroupName="address">
  <h3>Address</h3>

  <label>
    Street:
    <input type="text" formControlName="street">
  </label>

  <label>
    City:
    <input type="text" formControlName="city">
  </label>

  <label>
    State:
    <input type="text" formControlName="state">
  </label>

  <label>
    Zip Code:
    <input type="text" formControlName="zip">
  </label>
</div>

</form>

3.7 Partial model updates (Patching the model value)

Only updating parts of the model values of the whole FormGroup

updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: {
      street: '123 Drew Street'
    }
  });
}
<p>
  <button (click)="updateProfile()">Update Profile</button>
</p>

3.8 Generating form controls with FormBuilder

The FormBuilder service provides methods for generating controls

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

export class ProfileEditorComponent {
  profileForm = this.fb.group({
    firstName: [''],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
  });

  constructor(private fb: FormBuilder) { }
}

compare with the old settings, there is no need to write FormContrl for each element

profileForm = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl(''),
  address: new FormGroup({
    street: new FormControl(''),
    city: new FormControl(''),
    state: new FormControl(''),
    zip: new FormControl('')
  })
});

3.9 Simple form validation

In order to validate user input to ensure it's complete and correct Further details could be found in Form Validation

Here is only a single validator

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

profileForm = this.fb.group({
  firstName: ['', Validators.required],   <==== here the validator applied
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
});

HTML5 has a set of built-in attributes that you can use for native validation, including

Use these HTML5 validation attributes in combination with the built-in validators provided by Angular's reactive forms, notice the required in the following code

<input type="text" formControlName="firstName" required>

3.9 Complete Form Validation

3.9.1 Validator functions

Note: for performance reasons, Angular only runs async validators if all sync validators pass.

3.9.2 Built-in validators all built-in validators of Angular

e.g. required minlength all built-in validators of Angular[6]

ngOnInit(): void {
  this.heroForm = new FormGroup({
    'name': new FormControl(this.hero.name, [         <==== passed as the 2nd arguments
      Validators.required,                                           <==== passed as the 2nd arguments
      Validators.minLength(4),                                    <==== passed as the 2nd arguments
      forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. <==== passed as the 2nd arguments
    ]),
    'alterEgo': new FormControl(this.hero.alterEgo),
    'power': new FormControl(this.hero.power, Validators.required)
  });

}

get name() { return this.heroForm.get('name'); }

get power() { return this.heroForm.get('power'); }
<input id="name" class="form-control"
      formControlName="name" required >

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert alert-danger">

  <div *ngIf="name.errors.required">
    Name is required.
  </div>
  <div *ngIf="name.errors.minlength">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors.forbiddenName">
    Name cannot be Bob.
  </div>
</div>

3.9.3 Custom validators

Here is the Custom Validator for the above forbiddenNameValidator

forbidden-name.directive.ts (forbiddenNameValidator)

export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {'forbiddenName': {value: control.value}} : null;
  };
}

3.9.3.1 Adding to reactive forms

this.heroForm = new FormGroup({
  'name': new FormControl(this.hero.name, [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
  ]),
  'alterEgo': new FormControl(this.hero.alterEgo),
  'power': new FormControl(this.hero.power, Validators.required)
});

3.9.3.2 Adding to template-driven forms (not complete, not related right now)

3.9.4 Control status CSS classes

The following classes are currently supported:

3.9.5 Async Validation (not complete)

3.10 Displaying form status

Simply using interpolation.

<p>
  Form Status: {{ profileForm.status }}
</p>

3.11 Dynamic controls using FormArray

FormArray is an alternative to FormGroup

For FormArray don't need to define a key for each control by name, so this is a great option if you don't know the number of child values in advance.

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

profileForm = this.fb.group({        <====
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
  aliases: this.fb.array([           <==== see the differences
    this.fb.control('')                <==== only one
  ])
});

  get aliases() {
    return this.profileForm.get('aliases') as FormArray;
  }

// Because the returned control is of the type AbstractControl, you need to provide an explicit type to access the method syntax for the form array instance.

  addAlias() {
    this.aliases.push(this.fb.control(''));
  }
<div formArrayName="aliases">
  <h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>

  <div *ngFor="let address of aliases.controls; let i=index">
    <!-- The repeated alias template -->
    <label>
      Alias:
      <input type="text" [formControlName]="i">
    </label>
  </div>
</div>

Reference

[1] https://angular.io/guide/reactive-forms [2] https://code.tutsplus.com/tutorials/angular-form-validation-with-reactive-and-template-driven-forms--cms-32131 [3] https://malcoded.com/posts/angular-fundamentals-reactive-forms/ [4] https://alligator.io/angular/reactive-forms-introduction/ [5] https://angular.io/guide/forms-overview [6] https://angular.io/api/forms/Validators

Aakashdeveloper commented 4 years ago

Check this repo for various different froms sample https://github.com/Aakashdeveloper/Edu_Aug_Ang_19