简体   繁体   中英

Handle multiple Angular observables

I am a beginner with Angular and TypeScript, so I am creating my own application using the public Pokemon API for practice. However, I want some feedback on the task to use http to get information on the pokemons with ids between [1, 50]. To get the Pokemon for each id, I need a separate http GET request. As a result, I have 50 Observable objects that I need to subscribe to and then generate an array out of the results from all 50 Observables. However, I want some advice on if there exists a better way to achieve what I am hoping to.

poke-dashboard.component.ts (Code to subscribe to 50 Observables)

import { Component, OnInit } from '@angular/core';
import { PokemonService } from '../services/pokemon.service';
import { Pokemon } from '../shared/pokemon';

@Component({
  selector: 'app-poke-dashboard',
  templateUrl: './poke-dashboard.component.html',
  styleUrls: ['./poke-dashboard.component.scss'],
})
export class PokeDashboardComponent implements OnInit {
  pokemons: Pokemon[];

  constructor(private pokemonservice: PokemonService) {}

  ngOnInit(): void {
    let pokemonsList: Pokemon[] = [];
    for (var i = 1; i <= 50; i++) {
      this.pokemonservice.getPokemonForId(i).subscribe((data: any) => {
        pokemonsList.push(this.pokemonservice.generatePokemon(data));
      });
    }
    this.pokemons = pokemonsList;
  }
}

pokemon.service.ts (Code to handle http calls)

import { Injectable } from '@angular/core';
import { Pokemon } from '../shared/pokemon';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class PokemonService {
  baseUrl: string;
  private pokemons: Pokemon[] = [
    new Pokemon(
      'pikachu',
      'pikachu',
      90,
      50,
      50,
      40,
      55,
      35,
      'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png'
    ),
  ];
  constructor(private httpClient: HttpClient) {
    this.baseUrl = 'https://pokeapi.co/api/v2/';
  }

  public getPokemonForId(id: number): Observable<any> {
    return this.httpClient.get(this.baseUrl + `pokemon/${id}`);
  }

  public generatePokemon(pokeinfo: any): Pokemon {
    return new Pokemon(
      pokeinfo['name'],
      pokeinfo['species']['name'],
      pokeinfo['stats'][0]['base_stat'],
      pokeinfo['stats'][1]['base_stat'],
      pokeinfo['stats'][2]['base_stat'],
      pokeinfo['stats'][3]['base_stat'],
      pokeinfo['stats'][4]['base_stat'],
      pokeinfo['stats'][5]['base_stat'],
      pokeinfo['sprites']['front_default']
    );
  }

  public getPokemons() {
    return this.pokemons;
  }
}

If you starting with Angular and RxJS, I wouldn't recommend PokeAPI to learn it with. It is a little complicated to obtain information from.

Neverthless, it contains hypertext media in the result. Instead of using a for loop, you could obtain URLs for each ID using this hypertext media. A GET request to the URL https://pokeapi.co/api/v2/pokemon/?limit=3&offset=0 fetches the following result.

{
  "count": 964,
  "next": "https://pokeapi.co/api/v2/pokemon/?offset=3&limit=3",
  "previous": null,
  "results": [
    {
      "name": "bulbasaur",
      "url": "https://pokeapi.co/api/v2/pokemon/1/"
    },
    {
      "name": "ivysaur",
      "url": "https://pokeapi.co/api/v2/pokemon/2/"
    },
    {
      "name": "venusaur",
      "url": "https://pokeapi.co/api/v2/pokemon/3/"
    }
  ]
}

You could make a request with parameter limit=50 if you need the first 50 entries. Now we could obtain all the URLs and make the HTTP requests. Instead of subscribing to it individually, we could use RxJS forkJoin which allows us to subscribe to multiple observables.

If you are starting with Angular, it might a little too much. I'd say to go at it one thing at a time. I'd recommend you to start with the Angular Tutorial . It introduces the basics.

Working example:Stackblitz

pokemon-api.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';

import { of, forkJoin } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';

export const BASE_URL = 'https://pokeapi.co/api/v2';

@Injectable()
export class PokemonApiService {

  constructor(private httpClient: HttpClient) { }

  public getListOfPokemon(limit: string, offset ?: string) {
    const params = new HttpParams()
      .set('offset', offset ? offset : '0')
      .set('limit', limit);
    return this.httpClient
      .get<any>(`${BASE_URL}/pokemon`, { observe: 'response', params: params })
      .pipe(
        map(res => res.body.results),
        catchError(error => of(error.url))
      );
  }

  public getPokemonDetails(urlList: Array<any>) {
    urlList = urlList.map(url => this.httpClient.get<any>(url));
    return forkJoin(urlList);
  }
}

app.component.ts

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map, catchError, tap } from 'rxjs/operators';

import { PokemonApiService } from './pokemon-api.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  pokemons = [];
  limit = '50';

  constructor(private _pokemonApiService: PokemonApiService) {}

  ngOnInit(){
    this.getPokemonList();
  }

  private getPokemonList() {
    this._pokemonApiService.getListOfPokemon(this.limit).subscribe(
      response => { this.getPokemonDetails(response.map(response => response.url)); },
      error => { console.error(error); }
    );
  }

  private getPokemonDetails(urlList: Array<string>) {
    this._pokemonApiService.getPokemonDetails(urlList).subscribe(
      response => { this.pokemons = response; },
      error => { console.error(error); }
    );
  }

}

app.component.html

<table>
  <tr>
    <th>ID</th>
    <th>Name</th>
    <th>Sprite</th>
  </tr>
  <tr *ngFor="let pokemon of pokemons">
    <td>{{ pokemon.id }}</td>
    <td>{{ pokemon.name | titlecase }}</td>
    <td><img [src]="pokemon.sprites.front_default"/></td>
  </tr>
</table>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM