Proyek Angular — Angular

Saatnya membangun proyek mini menggunakan konsep Angular yang sudah dipelajari! Spesifikasi: Contact List App Buat aplikasi Contact List dengan fitur: Tambah…

Proyek ini menyatukan semua yang sudah kamu pelajari jadi satu halaman pesanan Martabak Bang Jali yang utuh: menu dari @for, keranjang di service dengan signal dan computed, pipe rupiah, dan form yang mengirim pesanan lalu mengosongkan dirinya. Bayangin hari pertama warung buka. Papan menu, buku pesanan, dan kotak kasir sudah kamu coba satu per satu; hari ini semuanya dipakai bersamaan, dan angkanya harus cocok satu sama lain. Kalau kasir mencatat total di kertas sendiri, cepat atau lambat totalnya beda dengan isi buku pesanan.

🧪 Tebakan cepat: tambah() di KeranjangStore ditulis this.isi().push(item), bukan update. Pembeli klik Pesan dua kali, dan daftar keranjang memang menampilkan 2 baris. Badge Keranjang: ... item menunjukkan berapa? Jawabannya ada di bagian "Satu sumber, diubah lewat update".

Isi proyeknya

bang-jali/
  angular.json            konfigurasi workspace: build, serve, test
  package.json
  tsconfig.json
  src/
    index.html            <app-root></app-root>
    main.ts               bootstrapApplication(App, appConfig)
    app/
      app.config.ts       provider untuk seluruh aplikasi
      app.ts              halaman: menu, keranjang, form
      keranjang.store.ts  signal + computed, satu sumber kebenaran
      menu.ts             interface Menu dan daftar menunya
      rupiah.pipe.ts      40000 jadi Rp40.000

Halaman dan store-nya

// src/app/keranjang.store.ts
@Injectable({ providedIn: 'root' })
export class KeranjangStore {
  private isi = signal<Item[]>([])

  readonly daftar = this.isi.asReadonly()
  readonly jumlah = computed(() => this.isi().length)
  readonly total = computed(() => this.isi().reduce((t, item) => t + item.harga, 0))

  tambah(item: Item) {
    this.isi.update((isi) => [...isi, item])
  }

  buang(index: number) {
    this.isi.update((isi) => isi.filter((_, i) => i !== index))
  }

  kosongkan() {
    this.isi.set([])
  }
}
<!-- template App (src/app/app.ts) -->
<p class="badge">Keranjang: {{ store.jumlah() }} item, {{ store.total() | rupiah }}</p>

@for (m of menu; track m.id) {
  <p>
    {{ m.nama }}: {{ m.harga | rupiah }}
    <button (click)="store.tambah(m)" [disabled]="m.stok === 0">Pesan</button>
  </p>
}

<ul>
  @for (item of store.daftar(); track $index) {
    <li>{{ item.nama }} <button (click)="store.buang($index)">Buang</button></li>
  } @empty {
    <li>Keranjang masih kosong</li>
  }
</ul>

<form (ngSubmit)="kirim()">
  <input name="nama" [(ngModel)]="nama" placeholder="Nama" />
  <button type="submit">Kirim pesanan</button>
</form>
kirim() {
  if (!this.nama.trim() || this.store.jumlah() === 0) {
    return
  }
  this.nomor++
  const total = 'Rp' + this.store.total().toLocaleString('id-ID')
  this.terakhir.set(`Pesanan #${this.nomor} atas nama ${this.nama}: ${this.store.jumlah()} item, ${total}`)
  this.store.kosongkan()
  this.nama = ''
}

Dijalankan di Playground, langkah demi langkah:

Aksi isi jumlah() total() Yang tampil
Pesan Martabak Manis Cokelat Keju 1 item 1 40000 Keranjang: 1 item, Rp40.000
Pesan Martabak Telur Spesial 2 item 2 90000 Keranjang: 2 item, Rp90.000
Buang baris pertama 1 item 1 50000 Keranjang: 1 item, Rp50.000
Nama Sinta, Kirim pesanan kosong 0 0 Pesanan #1 atas nama Sinta: 1 item, Rp50.000; Keranjang masih kosong; kolom Nama kosong

Terang Bulan Pandan (stok 0) gak bisa dipesan: [disabled]="m.stok === 0".

Satu sumber, diubah lewat update

Versi tebakan tadi, dengan this.isi().push(item), setelah dua klik Pesan:

Versi update Versi push
Daftar keranjang 2 baris 2 baris
Badge Keranjang: 2 item, Rp90.000 Keranjang: 0 item, Rp0
Kirim pesanan (nama terisi) pesanan tercatat gak terjadi apa-apa

Badge-nya tetap 0 item. Itu jawaban tebakannya. push mengubah array yang lama, jadi signal-nya gak pernah memberi tahu bahwa isinya berubah, dan computed tetap memakai hasil lamanya. Daftarnya ikut bertambah cuma karena klik membuat Angular menggambar ulang template dan @for membaca array yang sama. kirim() pun berhenti di baris pertama, karena jumlah() masih 0. Selalu ubah signal lewat update atau set dengan array baru.

Pola form: tambah, lalu kosongkan

Dua latihan proyek memakai pola yang sama dengan kirim():

  1. Daftar disimpan di signal<...>([]), totalnya computed(() => this.daftar().length).
  2. Tiap kolom di dalam <form> memakai [(ngModel)] plus name; tanpa name, Angular menolak dengan NG01352 (pelajaran Template-driven Forms).
  3. (ngSubmit) memanggil method yang mengabaikan isian kosong, menambah item baru dengan update((list) => [...list, baru]), lalu mengosongkan kolomnya (this.name = '').
  4. Hapus = update((list) => list.filter((x) => x.id !== id)), bukan menyembunyikan baris.

@empty di dalam <ul> menggambar sebuah <li> saat daftarnya kosong. Itu pas untuk Contact List, tapi Note Taking App memeriksa bahwa ul li benar-benar habis setelah Hapus, jadi di sana jangan taruh @empty di dalam <ul>.

ng build: dari src ke dist

ng build mengubah semua file src jadi file siap upload. Defaultnya production (lihat angular.json); ng build --configuration development membuat versi yang gak dipadatkan untuk debugging. Output aplikasi ini:

Initial chunk files   | Names         |  Raw size | Estimated transfer size
main-V7CNGQCQ.js      | main          | 155.05 kB |                43.94 kB
polyfills-RVUGEMAS.js | polyfills     |  34.59 kB |                11.33 kB

                      | Initial total | 189.63 kB |                55.27 kB

Application bundle generation complete. [2.878 seconds]

Hasilnya ada di folder dist/: dist/bang-jali/browser berisi index.html, main-V7CNGQCQ.js, dan polyfills-RVUGEMAS.js. Isi folder browser itu yang diunggah ke hosting. Nama file berganti tiap isi kodenya berubah, jadi browser gak memakai versi lama dari cache.

⚠️ Jebakan yang sering ketemu

TL;DR: Proyek Angular = workspace dengan angular.json (konfigurasi build, serve, test) dan src/app berisi file-file kecil yang masing-masing punya satu tugas. Simpan data bersama di service dengan signal, turunkan jumlah dan total dengan computed, dan ubah lewat update/set dengan array baru; push membuat daftar dan total gak cocok (2 baris, badge 0 item). Pola form: [(ngModel)] plus name, (ngSubmit), tambah dengan spread, lalu kosongkan kolomnya. ng build (default production) menulis hasilnya ke dist/bang-jali/browser, folder yang diunggah ke hosting.

Yang akan kamu pelajari