DEV Community

Connie Leung
Connie Leung

Posted on

3

Resource API Changes in Angular 20 - Query Resource in rxResource

Table of Contents

Introduction

Resource API has some changes in Angular 20.

  • request will be renamed to params
  • loader is renamed to stream in rxResource. Therefore, stream will be used for streaming and querying a resource.
  • ResourceStatus is a string union type.

In this blog post, I will show an example of using rxResource to paginate Pokemons.

The first example updates the URL reactively when previous or next buttons are clicked The rxResource function responds to the new URL by querying the Pokemons and displaying the results.

Query a Resource

  • Create a Pokemon Page Service

First, I define a PokemonPageService that pageinates Pokemons. The HTTP service makes a request to the Pokemon API to retrieve an array of Pokemon URL. Next, I use forkJoin to make multiple HTTP requests to retrieve the Pokemons. Finally, I combine the results so that the response includes the Pokemon count, the optional Previous URL, the Next URL, and an array of Pokemons.

@Injectable({
  providedIn: 'root'
})
export class PokemonPageService {
  private readonly http = inject(HttpClient);

  paginate(url: string): Observable<PokemonPageType> {
    return this.http.get<PokemonPageUrlType>(url).pipe(
      mergeMap((res) => {
        const pokemonUrls = res.results.map(({ url }) => url);
        const pokemons$ = pokemonUrls.map((pokemonUrl) => 
          this.http.get<PrePokemon>(pokemonUrl)
            .pipe(catchError(() => of(undefined)))
        )
        return forkJoin(pokemons$).pipe(
          map((pokemons) => 
            pokemons.filter((pokemon) => !!pokemon)
          ),
          map((results) => ({ ...res, results }))
        )
      })
    )
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Create rxResource to Retrieve Pokemons

The PokemonPageComponent creates a rxResource that calls the PokemonPageService whenever the url signal is updated. The request property is renamed to params and it is a reactive function that returns the value of the url signal. The loader property is renamed to stream. The function destructures the parameter and renames params to url. The url is passed to the service to obtain the Pokemons to display.

  // When URL updates, the rxResource retrieves a page of Pokemons 
  pokemonPageRef = rxResource<PokemonPageType, string>({
    params: () => this.url(),
    stream: ({ params: url }) => this.pokemonPageService.paginate(url),
    defaultValue: {
      count: 0,
      previous: '',
      next: '',
      results: []
    },
  });
Enter fullscreen mode Exit fullscreen mode
  • The url signal is updated when users click the Previous or Next buttons.
@if (pokemonPageRef.hasValue()) {
  @let pagination = pokemonPageRef.value();
  @for (result of pagination.results; track result.id) {
    <app-pokemon-row [result]="result" />
    <hr />
  }

  <div>
  @if (prevUrl()) {
    <button (click)="url.set(prevUrl())">Prev Page</button>
  }
  @if (nextUrl()) {
    <button (click)="url.set(nextUrl())">Next Page</button>
  }
  </div>
}
Enter fullscreen mode Exit fullscreen mode
  • The resource status is a string in v20. The possible values are idle, loading, reloading, error, resolved, and local.
@let status = pokemonPageRef.status();
@let isLoading = status === 'loading';
@let isResolved = status === 'resolved';
@let isReloading = status === 'reloading';

<p>{{ `Status: ${status}` }}</p>
<p>{{ `Is loading: ${isLoading}` }}</p>
<p>{{ `Is reloading: ${isReloading}` }}</p>
<p>{{ `Is resolved: ${isResolved}` }}</p>

<button (click)="this.pokemonPageRef.reload()">
    Reload
</button>
Enter fullscreen mode Exit fullscreen mode

When users click the previous or next button, the rxResource queries Pokemons and the status changes to loading. isLoading is true, isResolved and isReloading are false. When the resource returns the data successfully, the status becomes resolved. isLoading and isReloading are false, and isResolved is true.

When users click the Reload buttton, the rxResource invokes the reload method. The status first changes to reloading. isReloading is true, isLoading and isResolved are false. The last status is resolved, isReloading and isLoading are false, and the isResolved is true.

  • This is the complete listing of the PokemonPageComponent
@Component({
  selector: 'app-pokemon-page',
  imports: [PokemonRowComponent],
  templateUrl: './pokemon-page.component.html',
})
export class PokemonPageComponent {

  url = signal('https://pokeapi.co/api/v2/pokemon?limit=6');

  pokemonPageService = inject(PokemonPageService);

  // When URL updates, the rxResource retrieves a page of Pokemons 
  pokemonPageRef = rxResource<PokemonPageType, string>({
    params: () => this.url(),
    stream: ({ params: url }) => this.pokemonPageService.paginate(url),
    defaultValue: {
      count: 0,
      previous: '',
      next: '',
      results: []
    },
  });

  value = computed(() => this.pokemonPageRef.hasValue() ? 
    this.pokemonPageRef.value() : undefined
  );

  nextUrl = computed(() => this.value()?.next);
  prevUrl = computed(() => this.value()?.previous);
}
Enter fullscreen mode Exit fullscreen mode

Demos

Resources

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (0)