J'utilise angular 10 dans mon projet. J'ai des appels HTTP imbriqués dans la fonction ngOnInit ici à quoi ça ressemble:
ngOnInit(): void { let communetyid; this.route.data.subscribe(data => { this.route.params.subscribe(params => { if(data.category === "code") { this.dataService.getCode(params.code) .subscribe(resp => { communetyid = resp.results.communityId }); } else { communetyid = params.id } this.dataService.getCommunityById(communetyid) .subscribe(response => { this.community = response.results; ///other http calls }) }) }) })
Comme vous pouvez le voir dans le code ci-dessus, le dataService.getCommunityById
obtient comme paramètre communetyid, le communetyid obtient une valeur à l'intérieur de l'instruction if. En raison de la fonction asynchrone dataService.getCommunityById
déclenchée avant que communityid n'obtienne la valeur et que j'obtienne l'erreur dans la console.
Comment puis-je changer le code qui, lorsque dataService.getCommunityById
est déclenché, la valeur communetyid sera initialisée.
Je sais que je peux copier dataService.getCommunityById
intérieur de l'abonnement du dataService.getCode
mais je veux éviter la duplication de code.
5 Réponses :
Je pense que vous pouvez utiliser l'opérateur concatMap
Pour transformer une réponse en paramètres requis pour la requête consécutive, utilisez concatMap
L'opérateur concatMap()
s'abonne en interne à l'Observable renvoyé par sa fonction de projection et attend qu'il se termine en réémettant toutes ses valeurs.
Voici un exemple
function mockHTTPRequest(url) { return Observable.of(`Response from ${url}`) .delay(1000); } let urls = ['url-1', 'url-2', 'url-3', 'url-4']; let start = (new Date()).getTime(); Observable.from(urls) .concatMap(url => mockHTTPRequest(url)) .timestamp() .map(stamp => [stamp.timestamp - start, stamp.value]) .subscribe(val => console.log(val));
pouvez-vous fournir un exemple lié au code affiché.
concatMap
est l'opérateur à utiliser dans de tels cas. Vous pouvez lire plus de détails et trouver de l'inspiration pour des cas similaires dans cet article
Si vous souhaitez conserver le modèle d'origine que vous utilisiez (imbrication des abonnements), vous devez simplement déplacer l'appel http "getCommunityById" dans le rappel "dataService.getCode", et cela fonctionnera. Le point clé ici est de comprendre que "dataService.getCode" renverra un observable (des données qui ne sont pas déjà là) et que votre branche else fonctionne avec une valeur déjà présente (params.id). La bonne façon de résoudre ce problème est d'utiliser concatMap () à l'intérieur d'un tube () et d'enchaîner ces opérations. Dans le rappel concatMap (), vous gérerez votre logique de branche if-else où vous retournerez soit this.dataService.getCode ou Observable.of (params.id) et que dans le prochain concatMap (), vous utiliserez cette valeur comme argument pour this.dataService.getCommunityById.
Tomov, merci pour le message. Pouvez-vous s'il vous plaît montrer un exemple?
Vous pouvez envisager de chaîner vos Observables
utilisant des operators
ordre supérieur
params$ = this.route.paramMap.pipe( map(params => ({ id: params.get("id"), code: params.get("code"), category: params.get("category") })) );
export class ChildComponent implements OnInit { constructor( private route: ActivatedRoute, private dataService: DataService ) {} // Extract Parameters from the Activated Route params$ = this.route.paramMap.pipe( map(params => ({ id: params.get("id"), code: params.get("code"), category: params.get("category") })) ); // Get Community Id communityId$ = this.params$.pipe( mergeMap(({ category, code, id }) => category === "code" ? this.dataService .getCode(code) .pipe(map(({ results }) => results.communityId)) : of(id) ) ); // Get Community community$ = this.communityId$.pipe( switchMap((communetyId) => this.dataService.getCommunityById(communetyId)) ) ngOnInit() { this.community$.subscribe({ next: console.log }); } }
J'utilise paramMap
pour extraire les paramètres de ActivatedRoute
. À partir de Angular Docs, les params
sont obsolètes (ou à être obsolètes)
ActivatedRoute
contient deux propriétés qui sont moins capables que leurs remplacements et peuvent être déconseillées dans une future version Angular.
L'étape suivante, nous définissons une propriété communityId
dont la valeur dépend du mappage des params
en vérifiant la propriété category
. mergeMap
opérateur mergeMap
pour fusionner l'abonnement de params$
à communityId$
.
La dernière étape consiste à obtenir la community$
. Ici, nous utilisons switchMap
. Nous ne voudrions pas continuer avec une demande initiale si une nouvelle demande a été faite donc cet opérateur est le plus approprié ici
Je pense que cela fonctionne comme prévu uniquement si "code" est une chaîne de longueur 1, comme dans l'exemple. La raison est dans la façon dont mergeMap
est utilisé. mergeMap
veut, comme paramètre, une fonction qui retourne un Observable. Dans la solution proposée, la fonction passée en paramètre à mergeMap
est { category, code, id }) => category === "code" ? this.dataService.getCode(code).pipe(map(({ results }) => results.communityId)) : id
qui renvoie un Observable ou une chaîne. Mais une chaîne est un observable qui émet chacun de ses caractères. Vous pouvez voir cela en action si vous essayez avec un code
comme "123".
@Picci bonne observation, j'ai mis à jour la solution
Même si vous pouvez utiliser rxjs pour chaîner les observables. Il existe une autre approche simple pour résoudre votre problème. Si vous mettez quelque chose dans l'abonnement d'une observable, il est exécuté, vous devez donc le déplacer à l'extérieur en tant que fonction distincte. Cela devrait vous aider à éviter d'appeler votre fonction dataService.getCommunityById .
ngOnInit(): void { let communetyid; this.route.data.subscribe(data => { this.route.params.subscribe(params => { if(data.category === "code") { this.dataService.getCode(params.code) .subscribe((resp) => { communetyid = resp.results.communityId; getCommunityData(communetyid); }); } else { communetyid = params.id; getCommunityData(communetyid); } }) }) }) getCommunityData(communetyid) { this.dataService.getCommunityById(communetyid) .subscribe(response => { this.community = response.results; //other http calls }) }
C'est une approche alternative à l'utilisation de rxjs, c'est juste plus facile à comprendre. Vous pouvez également utiliser les opérateurs de rxjs comme concatMap, comme suggéré par Medhat Mahmoud et Muhammet Can TONBUL.
Changez votre code comme suit: -
ngOnInit(): void { let communetyid; this.route.data.subscribe(data => { this.route.params.subscribe(params => { const communityIdObs = date.category === 'code' ? this.dataService.getCode(params.code).pipe(map(resp => resp.results.communityId)) : of(params.id); communityIdObs.pipe(mergeMap(res => { return this.dataService.getCommunityById(communetyid); }).subscribe(response => { this.community = response.results; ///other http calls }); })
L'opérateur Mergemap gardera vos appels dans l'ordre et j'ai également modifié le code pour le rendre plus court.
Vous pouvez trouver une solution détaillée pour votre cas et des exemples courants d'utilisation de RxJs avec Http dans cet article