1
votes

Comment utiliser Observables pour la pagination au point de terminaison REST-API?

J'utilise Angular 7 et je crée une application Web récupérant des données à partir d'un point de terminaison REST-API. Les utilisateurs mettent leur chaîne de recherche dans un champ commun formControl qui est observé. Si la valeur change dans le champ de saisie, une requête http sera envoyée au point de terminaison de l'API. Enfin, j'obtiens la réponse http comme observable que je peux m'abonner. Mais le résultat de la requête peut également contenir plus de 100 éléments. Ensuite, le point de terminaison de l'API envoie un lien SUIVANT vers la deuxième page et ainsi de suite (pagination au point de terminaison de l'API).

Mon problème est maintenant, je ne trouve pas la bonne façon d'observer le champ d'entrée de recherche ET d'interagir à travers toutes les pages SUIVANT que je reçois du point de terminaison de l'API. Séparément, cela fonctionne comme un charme.

Est-ce que quelqu'un a une bonne pratique pour mon cas d'utilisation?

Mon fichier composant:

  public requestApi(requestUrl: string): Observable<ApiDataResponse> {
    return this.sendRequest(requestUrl).pipe(
      mergeMap(result => {
        if (result['next']) {
          return this.requestApi(result['next']);
        } else {
          return of(result);
        }
      })
    );
  }

Mon fichier de service:

export class GeomapService {
  retrieveUrl = new Subject<string>();

  constructor(private http: HttpClient) { }

  public retrieveApi(searchTerm: string): Observable<ApiDataResponse> {
    this.retrieveUrl.next(baseApiUrl + '?q=' + searchTerm);
    return this.retrieveUrl.pipe(
      switchMap(url => this.sendRequest(url)),
    ).subscribe(data => {
      if (data['next']) {
        this.retrieveUrl.next(data['next']);
      }
    });
  }

  public sendRequest(url: string, httpOptions = {}): Observable<ApiDataResponse> {
    return this.http.get<ApiDataResponse>(url);
  }

}

Malheureusement, j'obtiens l'erreur suivante:

TS2322: Le type «Abonnement» n'est pas attribuable au type «Observable».   La propriété '_isScalar' est absente dans le type Â' Abonnement '.

MISE À JOUR: En ce moment, je suis un peu plus loin car j'ai réalisé que je devais fusionner / concilier les observables entrants séquentiels dans le service (fournis par une boucle).

export class GeomapComponent implements OnInit {
  searchTerm = new FormControl();
  constructor(private geomapService: GeomapService) { }

  ngOnInit() {
    this.searchTerm.valueChanges.pipe(
      debounceTime(400),
      distinctUntilChanged(),
      switchMap(term => this.geomapService.retrieveApi(term))
    ).subscribe(data => {
      if (data['results']) {
       ......
      }
    }
  }
}

Néanmoins, je traîne toujours avec le bon opérateur de combinaison / transformation, car dans cette étape avec mergeMap je reçois sur abonnement juste le résultat de la dernière réponse. Mais je souhaite également que toutes les réponses reçues auparavant soient fusionnées en une seule valeur.

Quelqu'un a-t-il une idée de la façon dont les retours dans l'opérateur devraient ressembler?

MISE À JOUR : Mise à jour du code, problème de tableau supprimé ( ApiDataResponse [] -> ApiDataResponse)


5 commentaires

Dans votre code, les données sont de type ApiDataResponse [] , non ?! Comment se fait-il que vous ayez les propriétés data ['next'] et data ['result'] sur un tableau?


@fridoo: data ['next'] et data ['result'] sont des valeurs de la réponse de l'API.


oui, je le pensais. Mais vous renvoyez toujours / switchMap / mergeMap à un Observable . Ainsi, votre variable data ou result sera de type ApiDataResponse [] . <- notez que c'est un tableau.


@fridoo: Vous avez raison avec le problème de tableau mentionné. Je l'ai corrigé. Merci. Cependant ce n'était pas la solution à mon problème (avis pour n'importe qui d'autre).


Pourriez-vous mettre à jour le code de votre question.


3 Réponses :


-1
votes

Vous ne pouvez pas retourner l'abonnement. Utilisez switchMap pour renvoyer observable ou subsciebe


1 commentaires

Ouais, j'ai remarqué. Mais quelle pourrait être une solution dans ce cas, en travaillant avec deux observables?



1
votes

Donc, si je comprends bien, vous avez un observable sur le champ de recherche, et lorsque vous recherchez, vous appelez l'API pour obtenir des résultats. Si les résultats sont> 100, votre API envoie les 100 premiers résultats et vous demande de faire une autre requête pour les 100 suivants, jusqu'à ce qu'il n'y ait plus de résultats.

Pour moi, c'est un peu bizarre que vous deviez obtenir TOUS les résultats à la fois, ce n'est pas le point d'envoyer les 100 premiers résultats pour attendre que l'utilisateur demande les 100 suivants (par exemple, en cliquant sur " bouton page suivante "ou en faisant défiler jusqu'à une certaine limite)?

Je résoudrais ce problème en stockant les 100 premiers résultats initiaux dans le composant qui les affiche (probablement dans un BehaviourSubject), et lorsqu'il est temps d'obtenir la partie suivante des résultats (par exemple, lorsque la position de défilement atteint un certain montant ou lorsque l'utilisateur clique sur un bouton suivant), je demanderais alors les résultats suivants s'il y en a ...


1 commentaires

Merci pour votre idée. J'ai eu la même idée aussi mais j'ai besoin de toutes les données à la fois pour la visualiser dans son ensemble.



0
votes

Je suis d'accord avec @LaetitiaDallinge pour savoir s'il est vraiment nécessaire ou approprié de demander toutes les données en même temps. Vous devriez généralement essayer de minimiser le nombre de requêtes http que vous faites et de n'obtenir les données que lorsque cela est vraiment nécessaire.

Si vous devez obtenir toutes les données en même temps, vous pouvez utiliser expand :

import { EMPTY } from 'rxjs';
import { expand, reduce, map } from 'rxjs/operators';

// I presumed that you get an object of this type back from your API.
interface ApiDataResponse {
  next: string,
  results: Things[]
}

public fetchAllData(searchTerm: string): Observable<Things[]> {
  return this.http.get<ApiDataResponse>(baseApiUrl + '?q=' + searchTerm)
    .pipe( 
      // we recursively call the GET requests until there is no 'next' url
      expand(apiResponse => apiResponse.next
        ? this.http.get<ApiDataResponse>(apiResponse.next)
        : EMPTY;
      }),
      // we map the api response to the data we actually want to return
      map(apiResponse => apiResponse.results),
      // we reduce the data of all GET requests to a single array
      reduce((accData, data) => accData.concat(data), [])
    )
}


3 commentaires

Merci pour votre réponse. Vous avez absolument raison, le problème avec le tableau était juste une erreur de mon côté avec l'opérateur catchError qui a renvoyé un tableau vide. Je l'ai déjà réparé. Concernant votre code, j'obtiens une erreur avec l'instruction de retour empty () (TS2349: Impossible d'appeler une expression dont le type n'a pas de signature d'appel. Le type 'Observer ' n'a pas de signatures d'appel compatibles.)


Après quelques recherches, j'ai découvert que dans RxJS 6, je dois utiliser EMPTY au lieu de empty () (Source: stackoverflow.com/questions/38548407/... ) Avec votre code fonctionne plutôt bien. MERCI! Enfin je le modifierai pour mon cas d'utilisation et publierai une mise à jour de mon code dès que possible.


@ Mike42 Ok, je viens de lire que empty () est obsolète au profit de la constante EMPTY dans RxJS 6. J'ai mis à jour mon code avec empty () me fonctionne toujours dans RxJS 6. J'ai également ajouté une fonction map au tube et modifié ma fonction réduction en conséquence, car j'ai trouvé cela nécessaire. Heureux d'avoir pu aider. Si ma réponse a résolu votre problème, veuillez accepter et / ou voter pour cette réponse. Merci.