Cheatsheet: Angular - Reactive Forms
Liad Bercovitch
Software Engineer @ Ofek 324 Unit – IAF | specializing in high-scale Full-Stack Web Development of Operational Intelligence Systems using by Intelligence Units (8200) | Typescript, Angular, Node, React, Postgresql
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
<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?
src/app/profile-editor/profile-editor.component.ts (import)
import { Validators } from '@angular/forms';
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: ['']
}),
});
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.
src/app/profile-editor/profile-editor.component.ts (import)
import { FormArray } from '@angular/forms';
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('')
])
});
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(''));
}
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[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!
CEO and security engineer
4 个月???? ??? ?? ?? ??????! ??? ????? ???? ?????? ???: ?????? ????? ??? ??????? ?????? ??????, ?????? ?????? ??????,?????? ????? ????????. https://chat.whatsapp.com/IyTWnwphyc8AZAcawRTUhR
UX/UI SAAS Product Designer & Consultant ?? | Helping SAAS / AI companies and Startups Build Intuitive, Scalable Products.
4 个月???? ??? ?? ?? ???????? ??? ????? ???? ?????? ???: ?????? ????? ??? ??????? ?????? ??????, ?????? ?????? ??????,?????? ????? ????????. https://chat.whatsapp.com/IyTWnwphyc8AZAcawRTUhR
B.Sc Electrical and Electronic engineerig
1 年Outstanding!!
Software Engineer at Palo Alto Networks
1 年Very helpful!
BSc. Industrial Engineering and Management student at Ben Gurion University
1 年????!!