I have installed angular 19 and the guide mentions about the new API method called resource
.
I want to understand what value it brings to angular with respect to signals.
What was the problems with the signals API to fetch data from observables or promises, I want to understand the significance of resource API.
This can be highlighted using a simple use case, followed by comparing the resource APIs with pre angular 19 methods to achieve the same:
Motive: I want to reactively call an API based on certain signals, the API is dependant on the signals so it should react.
The resource
and rxResource
are effective API for making working reactively with asynchronous actions (Promises and Observables) and reduce the dependency on rxjs enabling you to effectively use only signals for these actions.
rxjs
operators to achieve reactivity.async
pipe to achieve reactivity.The requirement can be achieved by the below easier resource code, rather than the below complex methods:
rs = ResourceStatus;
id = model(1);
type = model('posts');
dataObj: ResourceRef<Todo> = resource({
request: () => ({
id: this.id(),
type: this.type(),
}),
loader: ({ request: { id, type } }: any) =>
fetch(`https://jsonplaceholder.typicode.com/${type}/${id}`).then(
(response) => {
return response.json();
}
),
});
@if(dataObj.status() === rs.Resolved) {
@let item = dataObj.value();
<div>{{item?.id}} | {{item?.title}}</div>
} @else {
...Loading...
}
<hr/>
<input [(ngModel)]="id"/>
<select [(ngModel)]="type">
<option value="todos">todo</option>
<option value="posts">posts</option>
</select>
Since we are using fetch, the code is longer that actually achievable through shorter code, using rxResource
as shown below.
rs = ResourceStatus;
http = inject(HttpClient);
id = model(1);
type = model('posts');
dataObj: ResourceRef<Todo> = rxResource({
request: () => [this.id(), this.type()],
loader: ({ request: [id, type] }: any) =>
this.http.get<Todo>(`https://jsonplaceholder.typicode.com/${type}/${id}`),
});
The html is the same as above but smaller.
Before the dawn of resource
and rxResource
, below were the method of fetching data from API using signals.
Problems: Too much code | multiple imports | not scalable | not maintainable | highly dependent on rxjs.
toObservable
to convert the signal to an observable to achieve reactivity:Explanation: We use toObservable
to convert the signals to observables, then we use [combineLatest
][combineLatest] from rxjs to accumulate the signals together, then use [switchMap
][switchMap] to trigger the API call from the signal-observables we created, then using async pipe, we can access this data and react to the changes.
http = inject(HttpClient);
id = model(1);
type = model('posts');
// use toObservable to convert the signal to an observable
dataObj: Observable<Todo> = combineLatest([
toObservable(this.id),
toObservable(this.type),
]).pipe(
// make the API call using switchMap
switchMap(
([id, type]: [number, string]): Observable<Todo> =>
this.http.get<Todo>(
`https://jsonplaceholder.typicode.com/${type}/${id}`
)
)
);
@if(dataObj | async; as item) {
<div>{{item.id}} | {{item.title}}</div>
}
<input [(ngModel)]="id"/>
<select [(ngModel)]="type">
<option value="todos">todo</option>
<option value="posts">posts</option>
</select>
toSignal
to convert the signal to observables (which is used to make the API) then to a signal to achieve reactivity:Explanation: We use toObservable
to convert the signals to observables, then we use [combineLatest
][combineLatest] from rxjs to accumulate the signals together, then use [switchMap
][switchMap] to trigger the API call from the signal-observables we created, then using toSignal
we get rid of the async pipe, or chain this to a computed
to perform calculations, we can access this data and react to the changes.
http = inject(HttpClient);
id = model(1);
type = model('posts');
// use toObservable to convert the signal to an observable
dataObj: Signal<Todo | undefined> = toSignal(
combineLatest([toObservable(this.id), toObservable(this.type)]).pipe(
// make the API call using switchMap
switchMap(
([id, type]: [number, string]): Observable<Todo> =>
this.http.get<Todo>(
`https://jsonplaceholder.typicode.com/${type}/${id}`
)
)
)
);
@if(dataObj(); as item) {
<div>{{item.id}} | {{item.title}}</div>
}
<input [(ngModel)]="id"/>
<select [(ngModel)]="type">
<option value="todos">todo</option>
<option value="posts">posts</option>
</select>
computed
to achieve reactivity along with async pipe:Explanation: We use computed
which reacts when the signals inside change. The computed will return an observable
which we can use with async
pipe to access the data. The problem with this approach is that the data in HTML get's rerendered when the observables change, so bad for performance.
http = inject(HttpClient);
id = model(1);
type = model('posts');
// use computed to react to the signals and create an observable, that has the latest data.
// the problem with this approach is that the data inside the if gets recreated on each emit
dataObj: Signal<Observable<Todo | undefined>> = computed(() => {
return this.http.get<Todo>(
`https://jsonplaceholder.typicode.com/${this.type()}/${this.id()}`
);
});
@if(dataObj() | async; as item) {
<div>{{item.id}} | {{item.title}}</div>
}
<hr/>
<input [(ngModel)]="id"/>
<select [(ngModel)]="type">
<option value="todos">todo</option>
<option value="posts">posts</option>
</select>
Apart from these there are several other methods using effect
also.