Je dois faire plusieurs requêtes GET pour charger des données à partir d'une page externe.
La réponse de la requête a peut renvoyer un indicateur indiquant qu'il y a plus de données à charger: "nextPage": "/ v1 / catalog / products? page = 2 & pageSize = 10",
Voici le code de ma fonction.
J'ai essayé d'implémenter une boucle do while mais Je ne pouvais pas le faire fonctionner. Je suppose qu'il y a aussi une façon plus intelligente de faire cela - peut-être Switchmap?
Ancienne version
getProducts(url, dataSoFar = []): Observable<any[]> {
if (!url) {
return of (dataSoFar);
} else {
url = ZUORA_URL + url;
}
return this.http.get<any>(url, { headers }).pipe(
switchMap(p => this.getProducts( p.nextPage, [...dataSoFar, ...p.data]))
);
}
getData() {
return this.getProducts('/v1/catalog/products');
}
Je veux charger les données complètes et les stocker en un seul endroit. Comment puis-je boucler jusqu'à ce qu'il n'y ait plus de page suivante? - le chargement d'une page après l'autre fonctionne maintenant mais j'ai encore du mal à stocker les réponses ...
Version mise à jour
loadCatalog() {
return new Promise((resolve, reject) => {
this.http.get<Catalog[]>(ZUORA_URL + '/v1/catalog/products?page=1&pageSize=10', { headers })
.pipe(map(data => data))
.subscribe(data => {
this.catalog = data;
resolve(true);
});
});
}
3 Réponses :
Je ne sais vraiment pas s'il est recommandé d'envelopper l'observable avec une promesse et de mettre à jour les données comme effet secondaire.
async loadAllCatalogs(URL) {
return new Promise((resolve, reject) => {
try {
let catalogs = [];
let data = await this.loadCatalog('/v1/catalog/products?page=1&pageSize=10');
catalogs.push(data); // store catalog as an array since there may be more results based on nextPage key
while(data.nextPage) {
data = await this.loadCatalog(data.nextPage);
catalogs.push(data);
}
resolve(catalogs);
}
} catch (e) {
reject(e);
}
});
}
Vous pouvez maintenant chaîner les requêtes pour récupérer les données comme ci-dessous
loadCatalog(URL) {
return new Promise((resolve, reject) => {
this.http.get<Catalog[]>(ZUORA_URL + URL , { headers })
.pipe(map(data => data))
.subscribe(data => {
resolve(data);
});
});
}
Probablement parce que vous encapsulez une observable avec une promesse au lieu d'utiliser flatMap ou toPromise . Cela n'a aucun sens. Les observables peuvent recevoir plusieurs valeurs. Les promesses ne peuvent pas. Pourquoi même utiliser l'observable si vous voulez le casser?
J'avais déjà ajouté un commentaire sous la question originale avant d'essayer de répondre et j'ai également intitulé ma réponse avec le même titre. Mais là encore, la requête HTTP GET NE renvoie PAS plusieurs valeurs, alors quel est le point? De plus, les promesses sont plus faciles à gérer avec async / await, les observables ne peuvent pas l'être, il y a donc une autre raison à la même chose :)
Et même si cela «casse» les conventions, c'est toujours du code de travail qui résout la douleur de l'auteur. Ça craint quand les gens ne peuvent pas réaliser ça: D
Vous pouvez utiliser expand pour appeler votre API de manière récursive et réduisez pour réduire tous les réponses à un seul tableau.
Dans votre Service (MyService):
private products: Product[];
loadProducts() {
this.myService.fetchData('/v1/catalog/products').subscribe(products =>
this.products = products as Product[]
)
}
Dans votre composant:
import { EMPTY } from 'rxjs';
import { expand, reduce, map } from 'rxjs/operators';
baseUrl = ZUORA_URL;
// Let's say you get an object like this from your API
interface ApiResponse {
nextPage: string,
data: any[]
}
public fetchData(apiEndpoint: string): Observable<any[]> {
return this.http.get<ApiResponse>(baseUrl + apiEndpoint, { headers })
.pipe(
// recursively call the GET requests until there is no further 'nextPage' url
expand(apiResponse => {
if (!apiResponse.nextPage) {
return EMPTY;
}
return this.http.get<ApiResponse>(apiResponse.nextPage, { headers });
}),
// map the api response to the data we actually want to return
map(apiResponse => apiResponse.data),
// reduce the data of all GET requests to a single array
reduce((accData, data) => accData.concat(data))
)
}
Salut Fridoo, l'application affiche une erreur après la troisième requête: ERREUR TypeError: Impossible de lire la propriété «concat» de undefined avez-vous une idée de la cause du problème?
@Malte essaie de spécifier un tableau vide comme germe dans la fonction reduction : reduction ((accData, data) => accData.concat (data), [])
Habituellement, lorsque des éléments sont paginés, vous ne souhaitez les récupérer que lorsqu'un utilisateur les demande activement. Cependant, je vais simplement répondre à votre question en l'état et laisser la conférence de côté.
Je ne sais pas comment vous auriez inclus nextPage lorsque vous attendez un tableau comme votre de niveau supérieur dans la réponse, donc je vais supposer que la réponse est en fait de la forme:
public loadProducts(url: string): Observable<Catalog[]> {
let nextPage: Subject<string> = new Subject<string>();
let products: Subject<Catalog[]> = new Subject<Catalog[]>();
nextPage.subscribe((url: string) => {
this.fetchProducts(url, products, nextPage);
}).add(() => products.complete());
return products;
}
private fetchProducts(url: string, products: Subject<Catalog[]>, nextPage: Subject<string>): void {
this.http.get<CatalogResponse>(url, { headers })
.subscribe(response => {
products.next(response.products);
if (response.nextPage) {
nextPage.next(response.nextPage);
} else {
nextPage.complete();
}
});
}
Pour ce faire, vous pouvez utiliser un Observable .
interface CatalogResponse {
nextPage: string | undefined;
products: Catalog[];
}
Vous voudrez vous assurer de mettre en œuvre une opération "annuler" ou "arrêter" au cas où cela se poursuivrait indéfiniment.
Hors sujet, mais je ne suis vraiment pas sûr qu'il soit recommandé d'envelopper l'observable avec une promesse et de mettre à jour les données comme effet secondaire.
stackoverflow.com/questions/38308866/... Regardez ceci. Cela pourrait résoudre votre problème