Two-Way Binding — Angular

Two-way binding menghubungkan data dua arah antara komponen dan template. [(ngModel)] import { FormsModule } from "@angular/forms" @Component({ imports…

Two-way binding mengikat satu properti di class dengan satu kotak isian di template, ke dua arah sekaligus: yang diketik di kotak langsung mengubah properti, dan properti yang diubah kode langsung mengubah isi kotak. Bayangin catatan pesanan di Martabak Bang Jali ditulis di papan tulis kecil yang menghadap dua arah. Pembeli menulis "pedas" dari depan, Bang Jali langsung membacanya dari belakang. Bang Jali menghapusnya, papan di depan pembeli ikut bersih. Satu papan, dua sisi.

🧪 Tebakan cepat: Kamu menulis <input [(ngModel)]="catatan" /> dengan catatan = 'tanpa daun bawang', tapi lupa imports: [FormsModule]. Saat halaman terbuka, apa isi kotaknya? Dan saat kamu mengetik, apakah <p>Catatan: ...</p> ikut berubah? Jawabannya ada di bagian "FormsModule: tanpa itu, ngModel gak dikenal".

[(ngModel)]: kotak isian dan properti jadi satu

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

@Component({
  selector: 'app-pesanan',
  standalone: true,
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="catatan" placeholder="Catatan" />
    <button (click)="catatan = ''">Hapus</button>
    <p>Catatan: {{ catatan }}</p>
  `,
})
export class Pesanan {
  catatan = 'tanpa daun bawang'
}
Kejadian Isi kotak catatan <p>
Render pertama tanpa daun bawang 'tanpa daun bawang' Catatan: tanpa daun bawang
Isi kotak diganti jadi "pedas" pedas 'pedas' Catatan: pedas
Klik Hapus (kosong) '' Catatan:

Latihan pertama memakai bentuk yang sama: <input [(ngModel)]="nama" placeholder="Ketik nama..." /> dan <p>Halo, {{ nama }}!</p>. Mengetik Sinta membuat paragrafnya berbunyi Halo, Sinta!.

Banana in a box: [( )] = [ ] + ( )

Kurung siku di luar, kurung biasa di dalam: bentuknya kayak pisang di dalam kotak, makanya disebut banana in a box. Sintaks [()] ini cuma singkatan dari dua binding yang sudah kamu kenal:

<input [(ngModel)]="catatan" />

<!-- sama persis dengan: -->
<input [ngModel]="catatan" (ngModelChange)="catatan = $event" />

FormsModule: tanpa itu, ngModel gak dikenal

ngModel bukan bagian dari template seperti @if. Dia directive yang tinggal di FormsModule, jadi komponen standalone harus mengimpornya: import { FormsModule } from '@angular/forms' di atas file, dan imports: [FormsModule] di @Component.

Kalau lupa, Angular gak mengenali ngModel. Kotaknya jadi input HTML biasa yang kosong saat halaman terbuka, walaupun catatan berisi tanpa daun bawang. Kamu tetap bisa mengetik di kotak itu, tapi <p> diam di Catatan: tanpa daun bawang, karena gak ada yang membawa ketikanmu ke class. Itu jawaban tebakannya. Console menulis:

NG0303: Can't bind to 'ngModel' since it isn't a known property of 'input' (used in the 'Pesanan' component template).

Two-way untuk komponenmu sendiri: [(jumlah)]

Banana in a box juga jalan di komponen buatanmu, asal pasangannya benar: satu @Input() bernama x dan satu @Output() bernama persis xChange.

@Component({
  selector: 'app-pilih-jumlah',
  standalone: true,
  template: `
    <button (click)="ubah(-1)">-</button>
    <span>{{ jumlah }}</span>
    <button (click)="ubah(1)">+</button>
  `,
})
export class PilihJumlah {
  @Input() jumlah = 1
  @Output() jumlahChange = new EventEmitter<number>()

  ubah(selisih: number) {
    const baru = this.jumlah + selisih
    if (baru < 1) {
      return
    }
    this.jumlah = baru
    this.jumlahChange.emit(baru)
  }
}
<!-- template Pesanan, dengan PilihJumlah di imports dan jumlah = 1 -->
<app-pilih-jumlah [(jumlah)]="jumlah" />
<p>Total: Rp{{ jumlah * 50000 }}</p>

💡 model(): pasangan input dan output dalam satu baris

Sejak Angular 17.2, jumlah = model(1) menggantikan pasangan @Input() dan @Output() jumlahChange. Nilainya dibaca jumlah() dan diubah dengan this.jumlah.set(baru), dan induknya tetap menulis [(jumlah)]="jumlah". Seperti input() di pelajaran sebelumnya, model() butuh Angular CLI: editor latihan dan Playground gak mengenalinya (NG0303: Can't bind to 'jumlah' since it isn't a known property of 'app-pilih-jumlah'), jadi di sini kita memakai pasangan dekoratornya.

⚠️ Jebakan yang sering ketemu

TL;DR: [(ngModel)]="catatan" mengikat kotak isian dan properti ke dua arah: nilai class mengisi kotak, ketikan mengubah class. Sintaks banana in a box [()] itu singkatan dari [ngModel]="catatan" (ngModelChange)="catatan = $event". ngModel tinggal di FormsModule, jadi komponen standalone butuh imports: [FormsModule]; tanpa itu kotaknya kosong, ketikan gak sampai, dan console menulis NG0303. Komponenmu sendiri bisa dipakai dengan [(jumlah)] kalau punya @Input() jumlah dan @Output() jumlahChange yang di-emit; model() adalah bentuk singkatnya di proyek Angular CLI.

Yang akan kamu pelajari