Learn Angular - Forms & Validation
Episode 7 of 24

Learn Angular - Forms & Validation

This episode covers Angular forms and validation: the differences between template-driven and reactive forms, using FormControl, FormGroup, and FormArray, built-in validation and custom validators, and dynamic forms with clear user feedback.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Forms are the main gateway for user interaction with an application — login, checkout, registration. Angular offers two different form approaches, each with its own strengths.

Episode 7 covers template-driven forms and reactive forms, using FormControl, FormGroup, and FormArray, built-in validation and custom validators, and dynamic forms with clear feedback. Reactive forms are the primary choice for serious applications, so that's where our main focus is.

Template-driven and Reactive Forms

The Fundamental Differences

  • Template-driven: the form model is defined in the template via ngModel. Good for simple forms with few fields.
  • Reactive forms: the form model is built in the TypeScript class with FormControl, FormGroup, and FormArray. More structured, easy to test, and better suited for complex logic.

Your First Reactive Form

JSBasic reactive form
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
 
@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="loginForm" (ngSubmit)="submit()">
      <input formControlName="email" placeholder="Email" />
      <input formControlName="password" type="password" placeholder="Password" />
      <button type="submit" [disabled]="loginForm.invalid">Masuk</button>
    </form>
  `,
})
export class LoginComponent {
  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required, Validators.minLength(8)]),
  });
 
  submit(): void {
    console.log(this.loginForm.value);
  }
}

FormGroup wraps multiple FormControls. The formGroup and formControlName directives connect the class to the template. The submit button is disabled while the form is invalid — that's reactive forms' built-in feedback.

FormControl, FormGroup, and FormArray

FormBuilder to Reduce Boilerplate

FormBuilder makes creating forms easier with the control, group, and array methods:

JSA form with FormBuilder
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
 
@Component({
  selector: 'app-pesanan',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="kirim()">
      <input formControlName="alamat" placeholder="Alamat" />
      <div formArrayName="items">
        @for (item of items.controls; track $index) {
          <div [formGroupName]="$index">
            <input formControlName="nama" placeholder="Nama item" />
          </div>
        }
      </div>
      <button type="submit">Kirim</button>
    </form>
  `,
})
export class PesananComponent {
  private readonly fb = inject(FormBuilder);
 
  form = this.fb.nonNullable.group({
    alamat: ['', Validators.required],
    items: this.fb.array([this.fb.group({ nama: [''] })]),
  });
 
  get items(): FormArray {
    return this.form.get('items') as FormArray;
  }
}

formArrayName and formGroupName enable dynamic lists: you can add or remove items inside a FormArray at runtime. This is the foundation for order item forms, contact lists, and similar structures.

Built-in Validation and Custom Validators

Built-in Validators

Angular provides Validators.required, Validators.email, Validators.minLength, Validators.maxLength, Validators.min, Validators.max, and Validators.pattern. Several can be combined in an array, like the login example above.

Custom Validators

When built-in validation isn't enough, write your own validator. A validator is a function that returns null if valid, or an error object if not:

JSCustom price validator
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
 
export function hargaPositif(): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const nilai = Number(control.value);
    if (nilai === null || nilai === undefined || isNaN(nilai) || nilai <= 0) {
      return { hargaTidakValid: { value: control.value } };
    }
    return null;
  };
}

The hargaPositif validator rejects non-numeric or zero values. It's used the same way as built-in validators: new FormControl('', [hargaPositif()]). Because validation can be at the control, group, or array level, cross-field logic like "start date before end date" can also be implemented.

Dynamic Forms and User Feedback

Displaying Error Messages

Good feedback means errors appear clearly and on time. A common pattern: only show the message when the field has been touched (touched) and is invalid.

HTMLDisplaying error messages
@if (email.invalid && email.touched) {
  <small class="error">
    @if (email.hasError('required')) {
      Email wajib diisi
    } @else if (email.hasError('email')) {
      Format email tidak valid
    }
  </small>
}

email.hasError('required') and hasError('email') check for specific error types. Combining invalid and touched prevents messages from appearing too early while the user is just starting to type.

Dynamic Forms

For forms whose structure changes based on user input — for example, the payment method choice determining additional fields — combine a dynamic FormGroup with @if control flow in the template. Reactive forms make this kind of scenario relatively easy, because the form structure lives entirely in the class.

Wrap Up

Key takeaways:

  • Template-driven forms suit simple forms; reactive forms for serious applications.
  • FormControl, FormGroup, and FormArray are the building blocks of reactive forms.
  • FormBuilder reduces form-creation boilerplate.
  • A validator is a function that returns null or an error object.
  • Combine invalid and touched for well-timed error feedback.
  • FormArray and @if control flow support dynamic forms.

In the next episode, episode 8, we'll cover HTTP and data fetching — using HttpClient for REST API integration, working with Observables and RxJS, handling errors with retry, and caching and request optimization strategies.