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 tanpaasync.toObservable(signal)ketika Anda butuh operator sepertidebounceTimeatauswitchMap.
Contohnya, pencarian di server dengan debounce:
readonly results = toSignal(
toObservable(this.search).pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((term) => this.api.search(term)),
),
{ initialValue: [] },
);Ringkasan
signaluntuk state yang dimiliki komponen.computeduntuk semua yang diturunkan; hasilnya di-cache dan dependensinya dilacak.input()untuk data dari parent, dipadukan dengancomputedsebagai penggantingOnChanges.effecthanya untuk efek samping di luar Angular.- Tetap pakai RxJS untuk stream async, dan hubungkan dengan
toSignalsertatoObservable.