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
)
3 Réponses :
Vous ne pouvez pas retourner l'abonnement. Utilisez switchMap pour renvoyer observable ou subsciebe
Ouais, j'ai remarqué. Mais quelle pourrait être une solution dans ce cas, en travaillant avec deux observables?
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 ...
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.
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), []) ) }
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
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 () code> 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.
Dans votre code, les
données
sont de typeApiDataResponse []
, non ?! Comment se fait-il que vous ayez les propriétésdata ['next']
etdata ['result']
sur un tableau?@fridoo:
data ['next']
etdata ['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 variabledata
ouresult
sera de typeApiDataResponse []
. <- 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.