Cheatsheet: Angular - Reactive Forms

Cheatsheet: Angular - Reactive Forms

All rights reserved to Angular official website.

Why?

In Reactive forms, inputs and values are provided as streams of input values, which can be accessed. It helps you to handle form inputs whose values change over time, validate them, and add or remove controls at run time.

How?

There are three steps to using form controls.

Step 1

Register the reactive forms module in your application. This module declares the reactive-form directives that you need to use reactive forms.

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

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

Step 2

Generate a new component and instantiate a new FormControl.

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-name-editor',
  templateUrl: './name-editor.component.html',
  styleUrls: ['./name-editor.component.css']
})
export class NameEditorComponent {
  name = new FormControl<string|null>('');
}         

Notice: Don't forget the type.

Step 3

Register the FormControl in the template

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

Display The Value

  • Through the valueChanges observable where you can listen for changes in the form's value in the template using AsyncPipe or in the component class using the subscribe() method
  • With the value property, which gives you a snapshot of the current value: (The displayed value changes as you update the form control element.)

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

Set Value

updateName() {
  this.name.setValue('Nancy');
}        
<button type="button" (click)="updateName()">Update Name</button>        

Grouping Form Controls

Forms typically contain several related controls. Just as a form control instance gives you control over a single input field, a form group instance tracks the form state of a group of form control instances.


Reactive forms provide two ways of grouping multiple related controls into a single input form:

Form group - Defines a form with a fixed set of controls that you can manage together.

Form array - Defines a dynamic form, where you can add and remove controls at run time.

Step 2

src/app/profile-editor/profile-editor.component.ts (form group)

interface LoginForm {
    email: FormControl<string>;
    password?: FormControl<string>;
}        
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = new FormGroup<LoginForm>({
    firstName: new FormControl('', {nonNullable: true}),
    lastName: new FormControl('', {nonNullable: true}),
  });
}        

Step 3

src/app/profile-editor/profile-editor.component.html (template form group)

<form [formGroup]="profileForm">

  <label for="first-name">First Name: </label>
  <input id="first-name" type="text" formControlName="firstName">

  <label for="last-name">Last Name: </label>
  <input id="last-name" type="text" formControlName="lastName">

</form>        


Submitting Form

src/app/profile-editor/profile-editor.component.html (submit event)

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">        

src/app/profile-editor/profile-editor.component.ts (submit method)

onSubmit() {
  doSomethingWith(this.profileForm.value);
}        


Validating Form Input

src/app/profile-editor/profile-editor.component.html (submit button)

<p>Complete the form to enable button.</p>
<button type="submit" [disabled]="!profileForm.valid">Submit</button>        

Nested Form Group

Step 2

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

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });
}        

Step 3

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

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

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

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

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


Set Value

There are two ways to update the model value:

setValue() - The setValue() method strictly adheres to the structure of the form group and replaces the entire value for the control. (Good in catch nesting errors.)

patchValue() - Replace any properties that you want, defined in the object that have changed in the form model.

updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: { // Notice: street is object inside the address property.
      street: '123 Drew Street'
    }
  });
}        

FormBuilder Service

Why?

The FormBuilder provides syntactic sugar that shortens creating instances of a FormControl, FormGroup, or FormArray. It reduces the amount of boilerplate needed to build complex forms.

How?

src/app/profile-editor/profile-editor.component.ts (import)

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

src/app/profile-editor/profile-editor.component.ts (constructor)

constructor(private fb: FormBuilder) { }        

src/app/profile-editor/profile-editor.component.ts (form builder)

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

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = this.fb.group({
    firstName: [''],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
  });

  constructor(private fb: FormBuilder) { }
}        

Validating form input

How?

  1. Import a validator function in your form component.

src/app/profile-editor/profile-editor.component.ts (import)

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

  1. Add the validator to the field in the form.

src/app/profile-editor/profile-editor.component.ts (required validator)

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

  1. Add logic to handle the validation status.

src/app/profile-editor/profile-editor.component.html (display status)

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

Creating Dynamic Forms

Form Array

Why?

FormArray is an alternative to FormGroup for managing any number of unnamed controls. As with form group instances, you can dynamically insert and remove controls from form array instances, and the form array instance value and validation status is calculated from its child controls. However, you 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.

How?

To define a dynamic form, take the following steps.

  1. Import the FormArray class.

src/app/profile-editor/profile-editor.component.ts (import)

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

  1. Define a FormArray control.

src/app/profile-editor/profile-editor.component.ts (aliases form array)

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

  1. Access the FormArray control with a getter method.

src/app/profile-editor/profile-editor.component.ts (aliases getter)

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

src/app/profile-editor/profile-editor.component.ts (add alias)

addAlias() {
  this.aliases.push(this.fb.control(''));
}        

  1. Display the form array in a template.

src/app/profile-editor/profile-editor.component.html (aliases form array template)

<div formArrayName="aliases">
  <h2>Aliases</h2>
  <button type="button" (click)="addAlias()">+ Add another alias</button>

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

Validating form input

ngOnInit(): void {
  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)
  });

}

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

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

Defining custom validators

shared/forbidden-name.directive.ts (forbiddenNameValidator)

/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {forbiddenName: {value: control.value}} : null;
  };
}        

Adding cross-validation to reactive forms

const heroForm = new FormGroup({
  'name': new FormControl(),
  'alterEgo': new FormControl(),
  'power': new FormControl()
});        

shared/identity-revealed.directive.ts

/** A hero's name can't match the hero's alter ego */
export const identityRevealedValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  const name = control.get('name');
  const alterEgo = control.get('alterEgo');

  return name && alterEgo && name.value === alterEgo.value ? { identityRevealed: true } : null;
};        

reactive/hero-form-template.component.html

<div *ngIf="heroForm.errors?.['identityRevealed'] && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
    Name cannot match alter ego.
</div>        

Control status CSS classes

  • .ng-valid
  • .ng-invalid
  • .ng-pending
  • .ng-pristine
  • .ng-dirty
  • .ng-untouched
  • .ng-touched
  • .ng-submitted

.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

.alert div {
  background-color: #fed3d3;
  color: #820000;
  padding: 1rem;
  margin-bottom: 1rem;
}

.form-group {
  margin-bottom: 1rem;
}

label {
  display: block;
  margin-bottom: .5rem;
}

select {
  width: 100%;
  padding: .5rem;
}        

Creating Asynchronous Validators

Why?

An async validator ensures that heroes pick an alter ego that is not already taken. New heroes are constantly enlisting and old heroes are leaving the service, so the list of available alter egos cannot be retrieved ahead of time. To validate the potential alter ego entry, the validator must initiate an asynchronous operation to consult a central database of all currently enlisted heroes.

How?

constructor(private alterEgoValidator: UniqueAlterEgoValidator) {}        
const alterEgoControl = new FormControl('', { asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)], updateOn: 'blur' });        


Good Luck!

Netanel Stern

CEO and security engineer

4 个月

???? ??? ?? ?? ??????! ??? ????? ???? ?????? ???: ?????? ????? ??? ??????? ?????? ??????, ?????? ?????? ??????,?????? ????? ????????. https://chat.whatsapp.com/IyTWnwphyc8AZAcawRTUhR

回复
Amichai Oron

UX/UI SAAS Product Designer & Consultant ?? | Helping SAAS / AI companies and Startups Build Intuitive, Scalable Products.

4 个月

???? ??? ?? ?? ???????? ??? ????? ???? ?????? ???: ?????? ????? ??? ??????? ?????? ??????, ?????? ?????? ??????,?????? ????? ????????. https://chat.whatsapp.com/IyTWnwphyc8AZAcawRTUhR

回复
Lior Shemer

B.Sc Electrical and Electronic engineerig

1 年

Outstanding!!

Liron Refaeli

Software Engineer at Palo Alto Networks

1 年

Very helpful!

Hadar Zion

BSc. Industrial Engineering and Management student at Ben Gurion University

1 年

????!!

要查看或添加评论,请登录

Liad Bercovitch的更多文章

  • Angular: Visibility Control: *ngIf vs. hidden

    Angular: Visibility Control: *ngIf vs. hidden

    In Angular, managing element visibility often involves choosing between two options: *ngIf and the hidden property…

    2 条评论
  • FlexBox + Scroll (v.2)

    FlexBox + Scroll (v.2)

    Trying to add scrollbar to a component Sound simple I know. Well I’m adding this here in case someone messed up (or for…

    6 条评论
  • Angular: Observables & Operators

    Angular: Observables & Operators

    All rights reserved to Rxjs official website. It's seems to be a topic that a lot of people don't really understand…

    3 条评论
  • FlexBox + Scroll

    FlexBox + Scroll

    CSS so basic. well, Sometimes CSS just remains a mystery.

    2 条评论
  • AndroSec-RL - Efficient Android Malware Detection

    AndroSec-RL - Efficient Android Malware Detection

    The Presentaion of AndroSec-RL's(BA's final project in Software & Information System Engineering @ BGU) Prototype in…

  • ?????? ?????? ?????? ?C

    ?????? ?????? ?????? ?C

    ?????? ???? ?????? ?? ?????? ????: ?????? ?????? ?????? ?????? ????? ????? ?? ???? ?? ?? ?????? ?????? ???? ????????…

社区洞察

其他会员也浏览了