1

I am trying to display data using rxResource. However, data returned is always undefined. However, inspecting the network tabs shows the data is indeed being fetched and I'm just doing something wrong on my component. What is the issue with this code? Because I can't seem to find what the problem is

API service

import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { httpResource } from "@angular/common/http";

@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);
  }

Component file:

import { Component, inject, signal } from '@angular/core';
import { ApiService } from '@services/api.service';
import { rxResource, toSignal } from '@angular/core/rxjs-interop';
import { distinctUntilChanged, map } from 'rxjs';

@Component({
  selector: 'app-blogs',
  templateUrl: './blogs.component.html',
  styleUrl: './blogs.component.scss'
})
export class BlogsComponent {

  apiService = inject(ApiService)
  query = signal('')

  rxBlog = rxResource({
    request: () => this.query(),
    loader: () =>
    this.apiService.GET(`https://api.nationalize.io/?name=${this.query()}`)
    .pipe(
      distinctUntilChanged(),
      map((blogs) => blogs),
    )
  })

  search(event: Event) {
    const { value } = event.target as HTMLInputElement;
    this.query.set(value);
  }
} 
 

// Template
<p>blogs works!</p>

<input (input)="search($event)" placeholder="Search user..."/>
    
    <br />
    <ul>
      @let error = rxBlog.error();

      @if (error) {
        <p>{{ error }}</p>
      }

      @if (rxBlog.isLoading()) {
        <p>Loading Blogs...</p>
      }

      @for (blog of (rxBlog.value()).country) {
        {{blog.country_id}}
      }
      
    </ul>

Json data looks like below:

{
  "count": 22997,
  "name": "steve",
  "country": [
    {
      "country_id": "KE",
      "probability": 0.0728971981962264
    },
  ]
}
0

1 Answer 1

1

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()],
});

Stackblitz Demo


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()],
});

Stackblitz Demo


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>
}
2
  • How would I fetch data using rxResource? Or what is a better approach?
    – kibet
    Commented 18 hours ago
  • Even after the source signal changes, I am unable to display data on the template
    – kibet
    Commented 18 hours ago

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.