-1
votes

Service HTTP déclenché avant l'initialisation du paramètre

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.


1 commentaires

Vous pouvez trouver une solution détaillée pour votre cas et des exemples courants d'utilisation de RxJs avec Http dans cet article


5 Réponses :


0
votes

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));


2 commentaires

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



1
votes

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.


1 commentaires

Tomov, merci pour le message. Pouvez-vous s'il vous plaît montrer un exemple?



1
votes

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")
    }))
  );

Explication du code

Extraire les paramètres

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

Voir un exemple de démonstration


2 commentaires

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



0
votes

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.


0 commentaires

0
votes

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.


0 commentaires