Langsung ke konten
~/rifqi_abrory

Halaman

Beranda
Tentang
Stack
Pengalaman
Proyek
Tulisan
Kontak
Semua proyek

Proyek

Avrist PINTAR — Agent Portal
Avrist e-Application
Avrist Academy — LMS CMS
Avrist e-Payment
PRUHub
SIYAP

Artikel

Angular signals untuk halaman admin sehari-hari
Typed reactive forms di Angular, tanpa any
Error API yang konsisten di ASP.NET Core dengan ProblemDetails
Meneruskan CancellationToken sampai ke lapisan terbawah di ASP.NET Core
Graceful shutdown untuk service HTTP di Go
Idempotency key: membuat API pembayaran aman untuk di-retry
Form kontak yang tahan spam dengan Next.js Server Actions
Form yang ramah offline di React Native: draft dan antrean submit
Validasi request dan penanganan error di Spring Boot 3
Keyset pagination di SQL Server: ketika OFFSET mulai lambat

Aksi

Ganti ke mode gelap
Read in English
Salin alamat email
Semua artikel

Angular signals untuk halaman admin sehari-hari

Panduan praktis signal, computed, input, dan effect, dengan contoh halaman daftar yang bisa difilter.

3 menit baca

Di halaman ini

Kenapa signals untuk layar admin

Aplikasi admin sebagian besar berisi daftar, filter, dan form. State-nya sederhana, tetapi ada di mana-mana: teks pencarian, status yang dipilih, nomor halaman, baris data dari API. Sebelum ada signals, state ini biasanya tersebar di field class, BehaviorSubject, dan async pipe.

Signals memberi Angular satu primitif kecil dan sinkron untuk state lokal. Artikel ini membangun halaman daftar kursus dengan kotak pencarian dan filter status untuk menunjukkan empat API yang paling sering Anda pakai.

signal: state milik Anda

import { Component, signal } from "@angular/core";

type Status = "all" | "published" | "draft";

@Component({
  selector: "app-course-page",
  templateUrl: "./course-page.html",
})
export class CoursePage {
  readonly search = signal("");
  readonly status = signal<Status>("all");
}

Membaca signal berarti memanggilnya seperti fungsi, search(), dan menulisnya dengan set atau update:

<input
  type="search"
  [value]="search()"
  (input)="search.set($any($event.target).value)"
  placeholder="Search courses"
/>

computed: state turunan tanpa subscription

Daftar yang sudah difilter bukan state tersendiri; ia diturunkan dari data dan filter. Itulah fungsi computed:

import { computed, inject } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";

export class CoursePage {
  private readonly api = inject(CourseApi);

  readonly search = signal("");
  readonly status = signal<Status>("all");

  // The HTTP observable becomes a signal; it starts as an empty list.
  readonly courses = toSignal(this.api.list(), { initialValue: [] });

  readonly visible = computed(() => {
    const term = this.search().trim().toLowerCase();
    const status = this.status();

    return this.courses().filter(
      (course) =>
        (status === "all" || course.status === status) &&
        course.title.toLowerCase().includes(term),
    );
  });

  readonly count = computed(() => this.visible().length);
}

visible hanya dihitung ulang ketika search, status, atau courses berubah, dan hasilnya di-cache di antaranya. Tidak ada yang perlu di-unsubscribe.

Template-nya memakai control flow bawaan:

<p>{{ count() }} courses</p>

@for (course of visible(); track course.id) {
  <app-course-row [course]="course" />
} @empty {
  <p>No courses match your filters.</p>
}

input: signal dari parent

Komponen anak menerima data lewat input(), yang juga berupa signal:

import { Component, computed, input } from "@angular/core";

@Component({
  selector: "app-course-row",
  template: `
    <h3>{{ course().title }}</h3>
    <span [class.muted]="isDraft()">{{ course().status }}</span>
  `,
})
export class CourseRow {
  readonly course = input.required<Course>();
  readonly isDraft = computed(() => this.course().status === "draft");
}

Karena input adalah signal, menurunkan nilai darinya dengan computed menggantikan sebagian besar pemakaian ngOnChanges.

effect: khusus efek samping

effect berjalan ketika signal yang dibacanya berubah. Pakai untuk hal di luar dunia Angular, seperti menyimpan filter ke localStorage atau menulis ke URL:

import { effect } from "@angular/core";

export class CoursePage {
  constructor() {
    effect(() => {
      localStorage.setItem("course-filters", JSON.stringify({ status: this.status() }));
    });
  }
}

Kesalahan yang umum adalah memakai effect untuk menyalin satu signal ke signal lain. Kalau nilainya bisa dihitung, pakai computed; lebih sederhana dan tidak memicu siklus change detection tambahan.

Signals dan RxJS bersama-sama

Signals tidak menggantikan RxJS. Panggilan HTTP, debounce, dan websocket tetap berupa stream. Fungsi penghubung membuat keduanya tetap ringkas:

  • toSignal(observable$) untuk membaca stream di template tanpa async.
  • toObservable(signal) ketika Anda butuh operator seperti debounceTime atau switchMap.

Contohnya, pencarian di server dengan debounce:

readonly results = toSignal(
  toObservable(this.search).pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap((term) => this.api.search(term)),
  ),
  { initialValue: [] },
);

Ringkasan

  • signal untuk state yang dimiliki komponen.
  • computed untuk semua yang diturunkan; hasilnya di-cache dan dependensinya dilacak.
  • input() untuk data dari parent, dipadukan dengan computed sebagai pengganti ngOnChanges.
  • effect hanya untuk efek samping di luar Angular.
  • Tetap pakai RxJS untuk stream async, dan hubungkan dengan toSignal serta toObservable.