Initial Points:
When you want to debounce
inputs or additional reactive manipulations using rxjs
, then rxResource
is the best option.
When you want to just make an API call and have reactivity present then httpResource
is the best option.
The httpResource
under the hood calls HttpClient
so there is no need for the service.
The advantage of httpResource
is the large reduction of boiler plate code, use to make simple API calls.
Using HttpResource:
You have a very basic fetch example, so rxResource
is not the best fit, instead go for httpResource
, where we only need to supply a url
inside a callback and it will reactively fetch the data when the source signals inside the callback change.
rxBlog = httpResource<ApiData>(
() => `https://api.nationalize.io/?name=${this.query()}`
);
Full Code:
import {
Component,
inject,
Injectable,
ResourceStatus,
signal,
} from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { bootstrapApplication } from '@angular/platform-browser';
import {
HttpClient,
httpResource,
provideHttpClient,
} from '@angular/common/http';
import { Observable } from 'rxjs';
export interface ApiDataCountry {
country_id: string;
probability: number;
}
export interface ApiData {
count: number;
name: string;
country: Array<ApiDataCountry>;
}
@Injectable({
providedIn: 'root',
})
export class ApiService {
private readonly httpClient = inject(HttpClient);
public GET<T>(path: string, options: any = {}) {
return this.httpClient.get<T>(path, options) as Observable<T>;
}
}
@Component({
selector: 'app-root',
template: `
<p>blogs works!</p>
<input (input)="search($event)" placeholder="Search user..."/>
<br />
<ul>
@let error = rxBlog.error();
@if (error) {
<p>{{ error }}</p>
} @else if (rxBlog.isLoading()) {
<p>Loading Blogs...</p>
} @else {
@let countryList = (rxBlog.value())?.country || [];
@for (blog of countryList; track blog) {
<li>{{blog.country_id}}</li>
}
}
</ul>
`,
})
export class App {
apiService = inject(ApiService);
query = signal('');
rs = ResourceStatus;
rxBlog = httpResource<ApiData>(
() => `https://api.nationalize.io/?name=${this.query()}`
);
search(event: Event) {
const { value } = event.target as HTMLInputElement;
this.query.set(value);
}
}
bootstrapApplication(App, {
providers: [provideHttpClient()],
});
Using RxResource:
The rxResource
will not trigger the API until the source signals change, so there is no need for distinctUntilChanged
. It also fetches the values once during initialization.
rxBlog = rxResource({
request: () => this.query(),
loader: () => this.apiService.GET(`https://api.nationalize.io/?name=${this.query()}`)
})
Below is a working example to demonstrate the rxResource in action:
import {
Component,
inject,
Injectable,
ResourceStatus,
signal,
} from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { bootstrapApplication } from '@angular/platform-browser';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface ApiDataCountry {
country_id: string;
probability: number;
}
export interface ApiData {
count: number;
name: string;
country: Array<ApiDataCountry>;
}
@Injectable({
providedIn: 'root',
})
export class ApiService {
private readonly httpClient = inject(HttpClient);
public GET<T>(path: string, options: any = {}) {
return this.httpClient.get<T>(path, options) as Observable<T>;
}
}
@Component({
selector: 'app-root',
template: `
<p>blogs works!</p>
<input (input)="search($event)" placeholder="Search user..."/>
<br />
<ul>
@let error = rxBlog.error();
@if (error) {
<p>{{ error }}</p>
} @else if (rxBlog.isLoading()) {
<p>Loading Blogs...</p>
} @else {
@let countryList = (rxBlog.value())?.country || [];
@for (blog of countryList; track blog) {
<li>{{blog.country_id}}</li>
}
}
</ul>
`,
})
export class App {
apiService = inject(ApiService);
query = signal('');
rs = ResourceStatus;
rxBlog = rxResource({
request: () => this.query(),
loader: () =>
this.apiService.GET<ApiData>(
`https://api.nationalize.io/?name=${this.query()}`
),
});
search(event: Event) {
const { value } = event.target as HTMLInputElement;
this.query.set(value);
}
}
bootstrapApplication(App, {
providers: [provideHttpClient()],
});
Additional Points:
It is always good to define an interface for your data fetched.
export interface ApiDataCountry {
country_id: string;
probability: number;
}
export interface ApiData {
count: number;
name: string;
country: Array<ApiDataCountry>;
}
There is a possibility that the country
property fetched can be undefined
, so I use let
and or operator ||
to provide a default value if it is undefined
.
@let countryList = (rxBlog.value())?.country || [];
@for (blog of countryList; track blog) {
<li>{{blog.country_id}}</li>
}