#angular #forms #validation #reactive-forms

Day 6 β€” Angular Forms: Template Forms, Reactive Forms, Validation & Signals

πŸ“˜ Day 6 β€” Angular Forms: Template Forms, Reactive Forms, Validation & Signals

Zero to Hero β€” Hands-on Angular Tutorial

Today you will learn:

  • βœ”οΈ Template-Driven Forms (Simple)
  • βœ”οΈ Reactive Forms (Powerful)
  • βœ”οΈ Form Validation (Required, Email, Pattern)
  • βœ”οΈ Handling Form Events
  • βœ”οΈ Error Messages
  • βœ”οΈ Integrating Signals with Forms
  • βœ”οΈ Saving Data to an API

Forms are the backbone of most applications. By the end of this, you’ll master both ways to build them.


🟦 1. Which Form Type Should You Choose?

FeatureTemplate-DrivenReactive Forms
Best ForSimple login, contact formsComplex, dynamic, enterprise apps
StructureDefined in HTMLDefined in TypeScript
ScalabilityHarder to scaleVery easy to scale
TestingHarderEasier

We will build BOTH.


🟩 2. Template-Driven Forms (The Easy Way)

Designed for simple use cases.

Step 1: Import FormsModule.

app.component.ts:

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

@Component({
  standalone: true,
  imports: [FormsModule],
  templateUrl: './app.component.html'
})
export class AppComponent {
  user = {
    name: '',
    email: ''
  };

  onSubmit() {
    console.log('Form Submitted!', this.user);
  }
}

Step 2: Build the HTML.

app.component.html:

<form (ngSubmit)="onSubmit()" #form="ngForm">
  <div>
    <label>Name:</label>
    <input type="text" name="name" [(ngModel)]="user.name" required />
  </div>

  <div>
    <label>Email:</label>
    <input type="email" name="email" [(ngModel)]="user.email" required email />
  </div>

  <button type="submit" [disabled]="form.invalid">Submit</button>
</form>
  • βœ”οΈ [(ngModel)] = Two-way binding
  • βœ”οΈ #form="ngForm" = Access form state
  • βœ”οΈ required email = HTML5-style validation

Simple. Fast. Effective.


🟧 3. Reactive Forms (The Pro Way)

Designed for complexity, validation, and control.

Step 1: Import ReactiveFormsModule.

app.component.ts:

import { Component, inject } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { NgIf } from '@angular/common';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule, NgIf],
  templateUrl: './app.component.html'
})
export class AppComponent {
  fb = inject(FormBuilder);

  loginForm = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(6)]]
  });

  onSubmit() {
    if (this.loginForm.valid) {
      console.log('Login Payload:', this.loginForm.value);
    }
  }
}

Step 2: Build the HTML.

app.component.html:

<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  
  <div>
    <label>Email:</label>
    <input formControlName="email" type="email" />
    
    <div *ngIf="loginForm.get('email')?.invalid && loginForm.get('email')?.touched">
      ❌ Invalid Email
    </div>
  </div>

  <div>
    <label>Password:</label>
    <input formControlName="password" type="password" />

    <div *ngIf="loginForm.get('password')?.hasError('minlength')">
      ⚠️ Password must be 6+ chars
    </div>
  </div>

  <button type="submit" [disabled]="loginForm.invalid">Login</button>

</form>
  • βœ”οΈ [formGroup] connect the form
  • βœ”οΈ formControlName binds inputs
  • βœ”οΈ Validation logic stays in TypeScript
  • βœ”οΈ Validators provides powerful rules

πŸŸ₯ 4. Advanced Validation (Signals + Custom Validators)

Let’s create a custom validator that forbids the name β€œAdmin”.

validators/no-admin.validator.ts:

import { AbstractControl, ValidationErrors } from '@angular/forms';

export function noAdminvalidator(control: AbstractControl): ValidationErrors | null {
  return control.value.toLowerCase() === 'admin' ? { isBlocked: true } : null;
}

Usage:

username: ['', [Validators.required, noAdminvalidator]]

Template Error:

<p *ngIf="form.get('username')?.hasError('isBlocked')">
  β›” You cannot use 'Admin' as a username.
</p>

🟫 5. Dynamic Forms (FormArray)

What if you need a dynamic list of skills?

app.component.ts:

skills = this.fb.array([]);

addSkill() {
  this.skills.push(this.fb.control(''));
}

app.component.html:

<div formArrayName="skills">
  <div *ngFor="let skill of skills.controls; let i = index">
    <input [formControlName]="i" placeholder="Skill {{i + 1}}" />
  </div>
</div>

<button (click)="addSkill()">+ Add Skill</button>
  • βœ”οΈ Great for β€œAdd multiple phone numbers” or dynamic fields.

🟦 6. Connecting Forms to API (CRUD Integration)

app.component.ts:

register() {
  if (this.form.valid) {
    this.http.post('https://api.example.com/register', this.form.value)
      .subscribe({
        next: () => alert('Success!'),
        error: () => alert('Error!')
      });
  }
}

πŸŽ‰ End of Day 6 β€” What You Learned

Today you specialized in data collection:

  • βœ”οΈ Template-Driven Forms: Quick & Easy
  • βœ”οΈ Reactive Forms: Scalable & Testable
  • βœ”οΈ Validators: Built-in & Custom
  • βœ”οΈ FormArray: Dynamic Lists
  • βœ”οΈ Form Submission: Handling data

You are now capable of building:

  • Login/Register pages
  • Checkout flows
  • Complex data entry systems

πŸ§ͺ Day 6 Challenge

Build:

πŸ‘‰ A β€œJob Application Form”

Requirements:

  1. Use Reactive Forms.
  2. Fields:
    • Name (Required)
    • Email (Required, Email format)
    • Experience (Number, must be > 0)
    • Skills (Dynamic FormArray - Add/Remove skills)
  3. Show error messages for invalid fields.
  4. Disable submit button if form is invalid.