R
Reactive Forms
Type-safe Angular reactive forms with validation, custom validators, and form arrays.
Code
typescript
1// registration-form.component.ts2import { Component, OnInit } from "@angular/core";3import {4 FormBuilder,5 FormGroup,6 FormArray,7 Validators,8 AbstractControl,9 ValidationErrors,10 ReactiveFormsModule,11} from "@angular/forms";12import { CommonModule } from "@angular/common";13 14// Form value interface15interface RegistrationForm {16 personalInfo: {17 firstName: string;18 lastName: string;19 email: string;20 };21 credentials: {22 password: string;23 confirmPassword: string;24 };25 addresses: Array<{26 street: string;27 city: string;28 zipCode: string;29 isPrimary: boolean;30 }>;31 preferences: {32 newsletter: boolean;33 notifications: ("email" | "sms" | "push")[];34 };35}36 37// Custom validators38function passwordMatchValidator(control: AbstractControl): ValidationErrors | null {39 const password = control.get("password");40 const confirmPassword = control.get("confirmPassword");41 42 if (password && confirmPassword && password.value !== confirmPassword.value) {43 return { passwordMismatch: true };44 }45 return null;46}47 48function strongPasswordValidator(control: AbstractControl): ValidationErrors | null {49 const value = control.value;50 if (!value) return null;51 52 const hasUpperCase = /[A-Z]/.test(value);53 const hasLowerCase = /[a-z]/.test(value);54 const hasNumber = /[0-9]/.test(value);55 const hasSpecial = /[!@#$%^&*]/.test(value);56 57 const valid = hasUpperCase && hasLowerCase && hasNumber && hasSpecial;58 59 return valid ? null : {60 strongPassword: {61 hasUpperCase,62 hasLowerCase,63 hasNumber,64 hasSpecial,65 },66 };67}68 69@Component({70 selector: "app-registration-form",71 standalone: true,72 imports: [CommonModule, ReactiveFormsModule],73 template: `74 <form [formGroup]="form" (ngSubmit)="onSubmit()">75 <!-- Personal Info -->76 <fieldset formGroupName="personalInfo">77 <legend>Personal Information</legend>78 79 <div class="form-field">80 <label for="firstName">First Name</label>81 <input id="firstName" formControlName="firstName" />82 @if (getError('personalInfo.firstName', 'required')) {83 <span class="error">First name is required</span>84 }85 </div>86 87 <div class="form-field">88 <label for="email">Email</label>89 <input id="email" type="email" formControlName="email" />90 @if (getError('personalInfo.email', 'email')) {91 <span class="error">Invalid email format</span>92 }93 </div>94 </fieldset>95 96 <!-- Credentials -->97 <fieldset formGroupName="credentials">98 <legend>Credentials</legend>99 100 <div class="form-field">101 <label for="password">Password</label>102 <input id="password" type="password" formControlName="password" />103 @if (getError('credentials.password', 'strongPassword')) {104 <span class="error">Password must contain uppercase, lowercase, number, and special character</span>105 }106 </div>107 108 <div class="form-field">109 <label for="confirmPassword">Confirm Password</label>110 <input id="confirmPassword" type="password" formControlName="confirmPassword" />111 </div>112 113 @if (form.get('credentials')?.hasError('passwordMismatch')) {114 <span class="error">Passwords do not match</span>115 }116 </fieldset>117 118 <!-- Addresses (FormArray) -->119 <fieldset>120 <legend>Addresses</legend>121 <div formArrayName="addresses">122 @for (address of addresses.controls; track $index; let i = $index) {123 <div [formGroupName]="i" class="address-group">124 <input formControlName="street" placeholder="Street" />125 <input formControlName="city" placeholder="City" />126 <input formControlName="zipCode" placeholder="Zip Code" />127 <label>128 <input type="checkbox" formControlName="isPrimary" />129 Primary130 </label>131 <button type="button" (click)="removeAddress(i)">Remove</button>132 </div>133 }134 </div>135 <button type="button" (click)="addAddress()">Add Address</button>136 </fieldset>137 138 <button type="submit" [disabled]="form.invalid">Register</button>139 140 <pre>{{ form.value | json }}</pre>141 </form>142 `,143})144export class RegistrationFormComponent implements OnInit {145 form!: FormGroup;146 147 constructor(private fb: FormBuilder) {}148 149 ngOnInit(): void {150 this.form = this.fb.group({151 personalInfo: this.fb.group({152 firstName: ["", [Validators.required, Validators.minLength(2)]],153 lastName: ["", [Validators.required]],154 email: ["", [Validators.required, Validators.email]],155 }),156 credentials: this.fb.group(157 {158 password: ["", [Validators.required, Validators.minLength(8), strongPasswordValidator]],159 confirmPassword: ["", [Validators.required]],160 },161 { validators: passwordMatchValidator }162 ),163 addresses: this.fb.array([this.createAddressGroup()]),164 preferences: this.fb.group({165 newsletter: [false],166 notifications: [["email"]],167 }),168 });169 }170 171 get addresses(): FormArray {172 return this.form.get("addresses") as FormArray;173 }174 175 createAddressGroup(): FormGroup {176 return this.fb.group({177 street: ["", Validators.required],178 city: ["", Validators.required],179 zipCode: ["", [Validators.required, Validators.pattern(/^\d{5}$/)]],180 isPrimary: [false],181 });182 }183 184 addAddress(): void {185 this.addresses.push(this.createAddressGroup());186 }187 188 removeAddress(index: number): void {189 this.addresses.removeAt(index);190 }191 192 getError(path: string, error: string): boolean {193 const control = this.form.get(path);194 return control ? control.hasError(error) && control.touched : false;195 }196 197 onSubmit(): void {198 if (this.form.valid) {199 const formValue: RegistrationForm = this.form.value;200 console.log("Form submitted:", formValue);201 } else {202 this.form.markAllAsTouched();203 }204 }205}Run this example locally
$ ng generate component registration-form