Episode ini membahas form dan validasi Angular: perbedaan template-driven dan reactive forms, penggunaan FormControl, FormGroup, dan FormArray, validasi built-in dan custom validator, serta dynamic forms dengan feedback pengguna yang jelas.

Form adalah gerbang utama interaksi pengguna dengan aplikasi — login, checkout, registrasi. Angular menawarkan dua pendekatan form yang berbeda, masing-masing dengan kelebihan sendiri.
Episode 7 membahas template-driven forms dan reactive forms, pemakaian FormControl, FormGroup, dan FormArray, validasi built-in dan custom validator, serta dynamic forms dengan feedback yang jelas. Reactive forms adalah pilihan utama untuk aplikasi serius, jadi fokus utama kita di sana.
ngModel. Cocok untuk form sederhana dan sedikit field.FormControl, FormGroup, dan FormArray. Lebih terstruktur, mudah dites, dan lebih cocok untuk logika kompleks.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 membungkus beberapa FormControl. Direktif formGroup dan formControlName menghubungkan class dengan template. Tombol submit nonaktif saat form invalid — itulah feedback bawaan reactive forms.
FormBuilder mempermudah pembuatan form dengan metode control, group, dan array:
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 dan formGroupName memungkinkan daftar dinamis: kalian bisa menambah atau menghapus item di dalam FormArray saat runtime. Ini dasar untuk form item order, daftar kontak, dan sejenisnya.
Angular menyediakan Validators.required, Validators.email, Validators.minLength, Validators.maxLength, Validators.min, Validators.max, dan Validators.pattern. Beberapa bisa digabung dalam array seperti contoh login di atas.
Saat validasi bawaan tidak cukup, tulis validator sendiri. Validator adalah fungsi yang mengembalikan null jika valid atau objek error jika tidak:
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;
};
}Validator hargaPositif menolak nilai non-angka atau nol. Pemakaiannya sama seperti validator bawaan: new FormControl('', [hargaPositif()]). Karena validasi bisa di-level control, group, atau array, logika antar field seperti "tanggal mulai sebelum tanggal selesai" juga bisa dibuat.
Feedback yang baik artinya error muncul jelas dan tepat waktu. Pola umum: tampilkan pesan hanya saat field sudah disentuh (touched) dan tidak valid.
@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') dan hasError('email') memeriksa jenis error spesifik. Kombinasi invalid dan touched membuat pesan tidak muncul terlalu dini saat pengguna baru mulai mengetik.
Untuk form yang strukturnya berubah tergantung input pengguna — misalnya pilihan metode pembayaran menentukan field tambahan — gunakan kombinasi FormGroup dinamis dan kontrol flow @if di template. Reactive forms membuat skenario seperti ini relatif mudah karena struktur form sepenuhnya berada di class.
Inti yang harus dibawa pulang:
FormControl, FormGroup, dan FormArray adalah blok bangunan reactive forms.FormBuilder mengurangi boilerplate pembuatan form.null atau objek error.invalid dan touched untuk feedback error yang tepat waktu.FormArray dan kontrol flow @if mendukung form dinamis.Di episode 8 selanjutnya kita akan membahas HTTP dan data fetching — memakai HttpClient untuk integrasi REST API, bekerja dengan Observables dan RxJS, menangani error dengan retry, serta strategi caching dan optimasi request.