HttpClient adalah service bawaan Angular untuk mengirim request HTTP (GET, POST, PUT, DELETE) ke server dan membaca jawabannya. Bayangin pelayan di warung Martabak Bang Jali. Kamu menulis pesanan di kertas, tapi pelayannya baru jalan ke dapur saat kamu menyerahkan kertas itu. Serahkan dua kertas, dia jalan dua kali. http.get() menulis kertasnya (sebuah Observable), dan subscribe() yang menyerahkannya.
🧪 Tebakan cepat: Constructor MenuPage menulis
this.api.ambilMenu(), tanpa.subscribe(). Berapa requestGET /api/menuyang terkirim ke server? Jawabannya ada di bagian "Belum subscribe, belum ada request".
Siapkan HttpClient
Di proyek Angular CLI, HttpClient didaftarkan sekali di app.config.ts:
import { ApplicationConfig } from '@angular/core'
import { provideHttpClient } from '@angular/common/http'
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()],
}
Lupa baris ini, dan service yang meminta HttpClient gagal dibuat dengan NG0201 (No provider found for ...) yang menyebut HttpClient; di Angular 18 namanya NullInjectorError. Editor latihan dan Playground menambahkannya sendiri begitu kodenya menyebut HttpClient.
GET dari service
Request ditaruh di service, seperti keranjang di pelajaran Services, supaya halaman mana pun bisa memakainya:
// src/app/menu-api.service.ts
import { Injectable, inject } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
export interface Menu {
id: number
nama: string
harga: number
stok: number
}
@Injectable({ providedIn: 'root' })
export class MenuApi {
private http = inject(HttpClient)
ambilMenu(): Observable<Menu[]> {
return this.http.get<Menu[]>('/api/menu')
}
}
Server menjawab dengan JSON, dan HttpClient mengubahnya jadi array JavaScript:
[
{ "id": 1, "nama": "Martabak Manis Cokelat Keju", "harga": 40000, "stok": 4 },
{ "id": 2, "nama": "Martabak Telur Spesial", "harga": 50000, "stok": 6 },
{ "id": 3, "nama": "Terang Bulan Pandan", "harga": 35000, "stok": 0 }
]
inject(HttpClient)meminta klien HTTP-nya, sama seperti meminta service lain.this.http.get<Menu[]>('/api/menu')menyiapkan request GET dan mengembalikanObservable<Menu[]>. Request-nya belum dikirim.<Menu[]>cuma janji ke TypeScript tentang bentuk jawabannya, bukan pemeriksaan. Kalau server mengirim"harga": "50000", isinya tetap teks.
Menampilkan: loading, data, error
// src/app/menu/menu.ts
import { Component, inject, signal } from '@angular/core'
import { HttpErrorResponse } from '@angular/common/http'
import { MenuApi, type Menu } from '../menu-api.service'
@Component({
selector: 'app-menu',
standalone: true,
template: `
<button (click)="muat()">Muat ulang</button>
@if (loading()) {
<p>Memuat menu...</p>
} @else if (error()) {
<p>Gagal: {{ error() }}</p>
} @else {
@for (m of menu(); track m.id) {
<p>{{ m.nama }}: {{ m.harga }}</p>
} @empty {
<p>Menu belum tersedia.</p>
}
}
`,
})
export class MenuPage {
private api = inject(MenuApi)
menu = signal<Menu[]>([])
loading = signal(true)
error = signal('')
constructor() {
this.muat()
}
muat() {
this.loading.set(true)
this.error.set('')
this.api.ambilMenu().subscribe({
next: (data) => {
this.menu.set(data)
this.loading.set(false)
},
error: (err: HttpErrorResponse) => {
this.error.set(err.message)
this.loading.set(false)
},
})
}
}
| Saat | Yang tampil |
|---|---|
| Request baru terkirim | Memuat menu... |
| Server menjawab 200 | tiga baris, mulai dari Martabak Manis Cokelat Keju: 40000 |
| Server gak bisa dihubungi | Gagal: Http failure response for /api/menu: 0 Unknown Error |
subscribe({ next, error })memisahkan jalan sukses dan jalan gagal.errbertipeHttpErrorResponse:err.statusberisi kode status (0 kalau server gak bisa dihubungi sama sekali, 404 kalau alamatnya gak ada),err.messageberisi kalimat seperti di tabel.loadingdimatikan di kedua jalan. Jangan taruh dicomplete: request yang gagal gak pernah memanggilcomplete, jadi halamannya tertahan di "Memuat menu..." selamanya.
Belum subscribe, belum ada request
Observable dari HttpClient itu dingin (cold): get() cuma menyiapkan request. Tanpa subscribe(), 0 request terkirim dan halamannya tertahan di "Memuat menu...". Itu jawaban tebakannya.
Sebaliknya, tiap subscribe() mengirim request baru. Dua subscribe() pada ambilMenu(), misalnya satu untuk daftar dan satu lagi untuk menghitung jumlah menu, berarti 2 request yang sama ke server. {{ (menu$ | async)?.length }} yang ditulis dua kali di template juga dua request. Ambil sekali, simpan hasilnya di signal, lalu baca signal itu di mana pun.
Kenapa Observable, bukan Promise? Karena bisa dibatalkan: kalau langganannya dihentikan sebelum server menjawab (misalnya async pipe saat pindah halaman), HttpClient membatalkan request-nya. Operator RxJS (map, catchError, retry) juga bisa dipasang di pipe()-nya.
catchError: cadangan saat gagal
Kalau gagal cukup diganti daftar kosong, pasang catchError di service:
import { catchError, of } from 'rxjs'
ambilMenu(): Observable<Menu[]> {
return this.http.get<Menu[]>('/api/menu').pipe(
catchError(() => of([])),
)
}
Saat server mati, catchError menangkap error-nya dan mengirim [] lewat next. Komponennya menerima daftar kosong, jadi @empty menampilkan Menu belum tersedia., error di subscribe gak pernah dipanggil, dan gak ada pesan gagal. Cocok untuk bagian sampingan; untuk halaman utama, biarkan error-nya sampai supaya pembeli tahu dan bisa Muat ulang.
Request lain bentuknya sama, dan juga baru terkirim saat di-subscribe: this.http.post('/api/pesanan', { menuId: 2, jumlah: 1 }).subscribe().
Latihan memakai pola service yang sama: ApiService dengan private http = inject(HttpClient) dan getData() yang mengembalikan this.http.get<any[]>('/api/data'), lalu komponen dengan private api = inject(ApiService) dan <p>Items: {{ items().length }}</p>.
⚠️ Jebakan yang sering ketemu
- Lupa subscribe.
get()tanpasubscribe()(atauasync) gak mengirim apa pun. - Subscribe dua kali. Tiap langganan = satu request. Simpan hasilnya di signal dan baca dari situ.
- Mematikan loading di
complete. Request yang gagal melompaticomplete; matikan dinextdanerror. - Lupa
provideHttpClient()di proyek CLI: NG0201 yang menyebutHttpClient(di Angular 18:NullInjectorError). - Detail latihan. Ambil
HttpClientdanApiServicedenganinject(), bukan lewatconstructor, dan biarkan paragrafnya diawaliItems:. Editor latihan gak punya server di/api/data; kalau kamu subscribe, request-nya gagal denganHttp failure response for /api/data: 0 Unknown Errordan paragrafnya tetapItems: 0. Itu wajar dan gak dinilai.
TL;DR: HttpClient (didaftarkan dengan provideHttpClient()) mengirim request HTTP; taruh di service dengan inject(HttpClient) dan this.http.get<Menu[]>('/api/menu'). Hasilnya Observable dingin: tanpa subscribe() gak ada request, dan tiap subscribe() mengirim request baru. Tampilkan tiga keadaan dengan signal loading, menu, dan error, dan matikan loading di next maupun error, bukan di complete. catchError(() => of([])) mengganti kegagalan dengan cadangan. HttpClient memakai Observable karena request-nya bisa dibatalkan dan bisa diolah operator RxJS.