Angular: From Zero to Expert

Angular: From Zero to Expert

Angular 22: Debounced Signal

How Angular turned signal debouncing into a first-class primitive — and why it returns a Resource.

Amos Isaila's avatar
Amos Isaila
Mar 30, 2026
∙ Paid
Angular 22: Debounced

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:

Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.

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 threw

  • debouncedSearch.isLoading() — true while a value is pending

  • debouncedSearch.error() — the error, if the source signal threw

Full Signature

User's avatar

Continue reading this post for free, courtesy of Amos Isaila.

Or purchase a paid subscription.
© 2026 Amos Isaila · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture