Episode ini membahas komunikasi API dan caching: HTTP interceptors untuk penanganan error terpusat, strategi caching untuk API responses, state transfer dan server-side rendering cache, serta optimasi performa network requests.

Di episode 8 kalian belajar dasar HTTP. Sekarang saatnya membuat komunikasi API kalian profesional: penanganan error yang terpusat, caching yang cerdas, dan request yang ringan.
Episode 14 membahas HTTP interceptors untuk penanganan error terpusat, strategi caching untuk API responses, state transfer dan SSR cache, serta optimasi performa network requests. Ini jembatan dari aplikasi yang berfungsi menjadi aplikasi yang tangguh.
Interceptor menangkap setiap request dan response HTTP, jadi tempat ideal untuk logika silang seperti error handling. Bentuk fungsional sejak Angular 17:
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { NotifService } from './notif.service';
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
const notif = inject(NotifService);
return next(req).pipe(
catchError((err: HttpErrorResponse) => {
if (err.status === 401) {
notif.tampil('Sesi habis, silakan login kembali', 'error');
} else if (err.status >= 500) {
notif.tampil('Terjadi kesalahan server', 'error');
}
console.error(`[${err.status}] ${req.method} ${req.url}`);
return throwError(() => err);
}),
);
};errorInterceptor menangani semua error dari semua request di satu tempat: status 401 memberi tahu sesi habis, status 5xx menampilkan pesan server. Tidak ada lagi try-catch di setiap service — cukup daftarkan interceptor di provideHttpClient(withInterceptors([errorInterceptor])).
Interceptor berjalan sesuai urutan pendaftaran untuk request dan terbalik untuk response. Interceptor auth sebaiknya dipasang sebelum error interceptor agar request selalu membawa token saat error dihitung.
Caching mencegah request berulang untuk data yang sama. Pendekatan RxJS paling sederhana adalah shareReplay seperti di episode 8, tetapi perlu kebijakan pembaruan:
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, shareReplay, tap, timer, switchMap } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class KatalogService {
private readonly http = inject(HttpClient);
private cache$: Observable<Produk[]> | null = null;
private readonly ttl = 300000;
listar(): Observable<Produk[]> {
if (!this.cache$) {
this.cache$ = this.http.get<Produk[]>('/api/produk').pipe(
shareReplay(1),
switchMap((data) =>
timer(this.ttl).pipe(switchMap(() => this.cache$ = null)),
),
);
}
return this.cache$;
}
}Setelah lima menit (TTL 300000 milidetik), cache$ di-set ke null sehingga request berikutnya dijalankan ulang. Ini memberi keseimbangan antara responsivitas dan kesegaran data.
Cache harus bisa dibatalkan saat data berubah. Metode umum: method invalidate() yang meng-set cache$ = null, dipanggil setelah operasi create, update, atau delete. Untuk data yang sering berubah, pertimbangkan cache berbasis tag atau key dengan pustaka khusus.
Saat halaman di-render di server (SSR), data yang sama bisa langsung dipindahkan ke browser sehingga klien tidak perlu request ulang. Angular menyediakan TransferState:
import { Injectable, inject, TransferState, makeStateKey } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { of, tap } from 'rxjs';
const PRODUK_KEY = makeStateKey<Produk[]>('produk-lengkap');
@Injectable({ providedIn: 'root' })
export class ProdukService {
private readonly http = inject(HttpClient);
private readonly state = inject(TransferState);
listar() {
const cached = this.state.get(PRODUK_KEY, null);
if (cached) {
return of(cached);
}
return this.http.get<Produk[]>('/api/produk').pipe(
tap((data) => this.state.set(PRODUK_KEY, data)),
);
}
}Di server, data diambil dan disimpan ke TransferState. Di browser, nilai langsung dibaca dari state sehingga tidak ada request kedua. Ini menghilangkan jeda dan mengurangi beban API untuk halaman yang sama.
Hasil render SSR itu sendiri bisa di-cache di CDN atau server. Halaman publik dengan data jarang berubah bisa disimpan sebagai cache statis dengan TTL tertentu, sementara halaman yang personalisasi tetap dirender per request.
Kurangi jumlah round-trip: gabungkan beberapa request yang independen dengan forkJoin, dan batalkan request yang sudah tidak relevan dengan switchMap. Kirim payload yang lebih kecil — minta hanya field yang dibutuhkan lewat query param atau sparse fields.
import { forkJoin } from 'rxjs';
forkJoin({
profil: this.http.get<Profil>('/api/profil'),
order: this.http.get<Order[]>('/api/order?limit=10'),
}).subscribe(({ profil, order }) => {
this.profil.set(profil);
this.order.set(order);
});forkJoin menunggu semua request selesai lalu mengembalikan satu objek gabungan — satu subscription untuk dua request paralel. Ini memangkas waktu tunggu dibanding memuat satu per satu.
Selalu ukur: pantau jumlah request per halaman, ukuran payload, dan waktu respons di jaringan yang lambat. Gunakan HttpClient dengan withFetch untuk performa fetch API, dan periksa waterfall network di DevTools untuk menemukan request yang boros atau berurutan padahal bisa paralel.
Inti yang harus dibawa pulang:
shareReplay plus TTL memberikan cache sederhana dengan pembaruan otomatis.TransferState memindahkan data dari server ke browser sehingga SSR tanpa request ganda.forkJoin dan ukur performa secara rutin.Di episode 15 selanjutnya kita akan membahas performance optimization — profiling dengan Angular DevTools, strategi change detection Default versus OnPush, trackBy dan lazy loading untuk code splitting, serta analisis bundle dan optimasi production.