Services dan DI — Angular

Services berisi logic bisnis, dan Dependency Injection menyediakannya ke komponen. Membuat Service @Injectable({ providedIn: "root" }) export class…

Service adalah class biasa yang menyimpan data atau logic yang dipakai bareng oleh banyak komponen. Dependency injection (DI) adalah cara Angular mengantarkan service itu ke komponen yang memintanya. Bayangin warung Martabak Bang Jali punya satu buku pesanan di meja kasir. Yang bikin martabak dan yang menerima pesanan gak bawa buku sendiri-sendiri; mereka minta buku yang sama ke Bang Jali. Buku itu service-nya, Bang Jali yang membagikannya itu injector, dan "minta buku" itu inject().

🧪 Tebakan cepat: MenuItem dan Pesanan masing-masing menulis keranjang = new KeranjangService(). Kamu klik Pesan di kartu dua kali. Pesanan menampilkan berapa item? Jawabannya ada di bagian "Satu instance untuk semua".

Service: class dengan @Injectable

// src/app/keranjang.service.ts
import { Injectable, signal } from '@angular/core'

@Injectable({ providedIn: 'root' })
export class KeranjangService {
  isi = signal<string[]>([])

  tambah(nama: string) {
    this.isi.update((isi) => [...isi, nama])
  }
}

inject(): meminta service

// src/app/menu-item/menu-item.ts
import { Component, Input, inject } from '@angular/core'
import { KeranjangService } from '../keranjang.service'

@Component({
  selector: 'app-menu-item',
  standalone: true,
  template: `
    <h3>{{ nama }}</h3>
    <button (click)="pesan()">Pesan</button>
  `,
})
export class MenuItem {
  @Input() nama = ''
  private keranjang = inject(KeranjangService)

  pesan() {
    this.keranjang.tambah(this.nama)
  }
}
// src/app/pesanan/pesanan.ts
import { Component, inject } from '@angular/core'
import { KeranjangService } from '../keranjang.service'

@Component({
  selector: 'app-pesanan',
  standalone: true,
  template: `<p>Keranjang: {{ keranjang.isi().length }} item</p>`,
})
export class Pesanan {
  keranjang = inject(KeranjangService)
}
Kejadian keranjang.isi() Pesanan tampil
Render pertama [] Keranjang: 0 item
Klik Pesan di kartu Martabak Telur Spesial ['Martabak Telur Spesial'] Keranjang: 1 item
Klik Pesan sekali lagi dua nama Keranjang: 2 item

Latihan memakai pola yang sama: DataService bertanda @Injectable({ providedIn: 'root' }) punya getMessage() yang mengembalikan 'Data dari service!', komponennya menulis private data = inject(DataService) lalu message = this.data.getMessage(), dan template-nya <p>{{ message }}</p>.

Satu instance untuk semua

Dengan new, tiap komponen bikin keranjang sendiri. MenuItem menambah ke keranjang miliknya, Pesanan membaca keranjang miliknya yang masih kosong. Jadi sesudah dua klik, Pesanan tetap Keranjang: 0 item. Itu jawaban tebakannya.

Dengan inject() dan providedIn: 'root', Angular memberi instance yang sama ke semua yang meminta:

Cara mendapat keranjang Jumlah instance Pesanan sesudah 2 klik
new KeranjangService() di tiap komponen satu per komponen Keranjang: 0 item
inject(KeranjangService) + providedIn: 'root' satu untuk semua Keranjang: 2 item

Halaman yang memakai data yang sama (keranjang, akun yang login, data dari server) jadi selalu sinkron.

providers di komponen: instance sendiri

Kadang kamu memang ingin instance terpisah. Daftarkan service di providers milik komponen:

@Component({
  selector: 'app-menu-item',
  standalone: true,
  providers: [KeranjangService],
  template: `...`,
})
export class MenuItem { /* sama seperti tadi */ }

Angular mencari service dari komponen itu ke atas: providers miliknya dulu, lalu induknya, terakhir root injector. Dengan baris ini tiap kartu MenuItem dapat keranjang baru. Klik dua kali di kartu pertama dan sekali di kartu kedua: kartu pertama memegang 2 item, kartu kedua 1 item, dan Pesanan (yang gak punya providers) memegang instance root yang tetap 0.

Kalau service-nya gak ketemu

Tanpa providedIn dan tanpa providers di mana pun, Angular gak punya resep untuk membuatnya. Komponen yang memintanya gagal dibuat, dan console menulis:

ERROR NG0201: No provider found for `KeranjangService`. Source: Environment Injector. Find more at https://v22.angular.dev/errors/NG0201

Hasilnya sama kalau class-nya lupa diberi @Injectable() sama sekali. Angular 18 menulis pesan yang sama dengan nama lain: NullInjectorError: No provider for KeranjangService!. Perbaikannya: tambahkan { providedIn: 'root' }, atau daftarkan di providers.

inject() juga cuma boleh dipanggil saat Angular sedang membuat sesuatu: di properti class atau di constructor. Di dalam method yang jalan karena klik, hasilnya error:

NG0203: The `KeranjangService` token injection failed. `inject()` function must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`. Find more at https://v22.angular.dev/errors/NG0203

Kenapa minta, bukan bikin sendiri

Komponen yang memakai inject() gak peduli dari mana keranjangnya datang. Di test, kamu bisa menukarnya dengan keranjang palsu tanpa mengubah satu baris pun di Pesanan:

TestBed.configureTestingModule({
  imports: [Pesanan],
  providers: [{ provide: KeranjangService, useValue: { isi: signal(['Martabak Telur Spesial']), tambah() {} } }],
})

Pesanan di test ini menampilkan Keranjang: 1 item. Itu keuntungan utama DI: testability (gampang dites), plus loose coupling, karena komponen gak terikat ke cara service dibuat.

⚠️ Jebakan yang sering ketemu

TL;DR: Service adalah class bertanda @Injectable({ providedIn: 'root' }) yang menyimpan data atau logic untuk banyak komponen. Komponen memintanya dengan inject(KeranjangService) (atau lewat constructor), bukan new, dan dengan providedIn: 'root' semua yang meminta dapat satu instance yang sama, jadi Pesan di MenuItem langsung terbaca di Pesanan. providers: [KeranjangService] di komponen memberi instance sendiri. Data yang dipakai bersama disimpan di signal, supaya komponen lain ikut diperbarui. Service yang gak terdaftar memicu NG0201 (No provider found for ...; di Angular 18 NullInjectorError), dan inject() di dalam method memicu NG0203. Karena komponen cuma meminta, service-nya bisa ditukar dengan versi palsu di test: itu testability.

Yang akan kamu pelajari