Angular 22: Debounced Signal
How Angular turned signal debouncing into a first-class primitive — and why it returns a Resource.
Before you start diving into this new topic you can review the full guide of resource() that was introduced in Angular 19.
TL;DR
Angular added a debounced() function that takes a signal and a wait time, and returns a Resource:
signal → debounced(signal, 500) → Resource<T>The resource’s value() holds the debounced value. Its status tells you whether the value is settled ('resolved'), still waiting ('loading'), or if the source threw ('error'). No RxJS, no setTimeout wrappers, no custom hooks. Just signals in, Resource out.
The Problem
You have a search input. The user types “Tokyo”. Without debouncing, you fire 5 HTTP requests: one per keystroke. That’s wasteful, potentially slow, and can cause race conditions if responses arrive out of order.
The RxJS way:
searchControl.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(query => this.http.get(`/api/search?q=${query}`))
).subscribe(results => { ... });The manual way:
let timer: ReturnType<typeof setTimeout>;
function onInput(value: string) {
clearTimeout(timer);
timer = setTimeout(() => {
// now do something with value
}, 500);
}Both work. Neither integrates with signals. You end up bridging between observables or callbacks and signals, losing reactivity along the way.
The real gap: there was no signal-native way to debounce a value and get a reactive, composable result back.
The Solution: debounced()
The API
import { debounced } from '@angular/core';
const search = signal('');
const debouncedSearch = debounced(() => search(), 500);debouncedSearch is a Resource<string>. It has everything you’d expect:
debouncedSearch.value()— the debounced value (updates only after 500ms of silence)debouncedSearch.status()—'resolved'when settled,'loading'while waiting,'error'if the source threwdebouncedSearch.isLoading()—truewhile a value is pendingdebouncedSearch.error()— the error, if the source signal threw



