I recently did a presentation at work about Angular Signals. One section comparing RxJS and Signals noted that RxJS remained better at asynchronous work. Consider, for example, a text field that takes user input and maps it into API search results.
const searchResults$ = queryValue.pipe(
switchMap(query => this.apiService.search(query))
);
During the presentation, I said the best way to create an asynchronous computed Signal was to use RxJS.
computed()
will not accept anasync
callback. It also does not provide adone()
function that can be called in a callback. You must synchronously return a value from yourcomputed()
callback.After some experimentation, I figured out how to do it. You actually have to go back to RxJS and wrap it with
toSignal()
.
Then a member of the audience raised his hand and asked if I knew about the resource
api. It was a new feature coming to Angular that had been announced just a day or two before my presentation.
You can read the pull request yourself. It creates a new function, resource()
, which is essentially asyncComputed()
.
To return to our previous example, here’s how you would use Signals to map user input into a list of search results with resource()
:
export class SearchComponent {
protected query = model('');
protected searchResults: Signal<string[]>;
constructor(apiService: ApiService) {
this.searchResults = resource({
request: this.query,
loader: async () => {
const query = this.query();
const response = await apiService.search(query);
return response.json();
}
});
}
}
This tells Angular, “if this.query
ever changes, ping the API using this async method and return the results in this.searchResults
, a Signal we can use anywhere in the class or template.”
resource()
will arrive with Angular 19. There’s a bunch of other stuff you can do with it too. At the moment, the only downside I can see is that it can’t easily do some RxJS things, like debouncing.
But you know what? resource()
is a promising start. I’m excited we got an official way to make async computed Signals.