2017-09-06 2 views
0

J'essaie de fusionner 2 requêtes HTTP en Angular/TypeScript, mais j'ai du mal à le faire fonctionner. La situation est illustrée à l'aide du tutoriel Angular 2/4 Tour of Heroes, qui utilise un Observable pour rechercher des héros par valeur "name" (https://angular.io/tutorial/toh-pt6#add-the-ability-to-search-by-name). Le code de héros-search.service.ts est comme suit:Comment fusionner 2 recherches dans le tutoriel Angular Heroes?

import { Injectable } from '@angular/core'; 
import { Http }  from '@angular/http'; 

import { Observable }  from 'rxjs/Observable'; 
import 'rxjs/add/operator/map'; 

import { Hero }   from './hero'; 

@Injectable() 
export class HeroSearchService { 

    constructor(private http: Http) {} 

    search(term: string): Observable<Hero[]> { 
    return this.http 
       .get(`api/heroes/?name=${term}`) 
       .map(response => response.json().data as Hero[]); 
    } 
} 

que je dois faire l'équivalent d'ajouter une deuxième valeur de chaîne pour « synonyme » à la classe Hero:

export class Hero { 
    id: number; 
    name: string; 
    synonym: string; 
} 

et ajouter un synonyme à chaque héros:

import { InMemoryDbService } from 'angular-in-memory-web-api'; 
export class InMemoryDataService implements InMemoryDbService { 
    createDb() { 
    const heroes = [ 
     { id: 0, name: 'Zero', synonym: 'little' }, 
     { id: 11, name: 'Mr. Nice', synonym: 'pleasant' }, 
     { id: 12, name: 'Narco', synonym: 'sleep' }, 
     { id: 13, name: 'Bombasto', synonym: 'loud' }, 
     { id: 14, name: 'Celeritas', synonym: 'vegetable' }, 
     { id: 15, name: 'Magneta', synonym: 'color' }, 
     { id: 16, name: 'RubberMan', synonym: 'hose' }, 
     { id: 17, name: 'Dynama', synonym: 'motor' }, 
     { id: 18, name: 'Dr IQ', synonym: 'smart' }, 
     { id: 19, name: 'Magma', synonym: 'volcanic' }, 
     { id: 20, name: 'Tornado', synonym: 'wind' } 
    ]; 
    return {heroes}; 
    } 
} 

Je peux alors rechercher en héros-search.service.ts par synonyme en substituant:

 .get(`api/heroes/?synonym=${term}`) 

Ma question est comment chercher le nom et le synonyme et renvoyer des correspondances avec le nom ou le synonyme. À partir de la documentation de ReactiveX, il semble que la réponse est d'utiliser l'opérateur Merge: http://reactivex.io/documentation/operators/merge.html

Cependant, j'ai du mal à faire fonctionner ça. J'ai essayé différentes façons d'utiliser la fusion, y compris la nouvelle version du code de héros search.service.ts ci-dessus qui suit:

import { Injectable } from '@angular/core'; 
import { Http }  from '@angular/http'; 

import { Observable }  from 'rxjs/Observable'; 
import 'rxjs/add/operator/map'; 
import 'rxjs/add/operator/merge'; 

import { Hero }   from './hero'; 

@Injectable() 
export class HeroSearchService { 

    constructor(private http: Http) {} 

    search(term: string): Observable<Hero[]> { 
    const nameResults = this.http.get(`api/heroes/?name=${term}`); 
    const synonymResults = this.http.get(`api/heroes/?synonym=${term}`);  
    nameResults.merge(synonymResults); 
    return nameResults.map(response => response.json().data as Hero[]); 
    } 
} 

Le code non-travail est correctement à https://plnkr.co/edit/4mrL5ReQCSvuzOODixJU?p=preview.

Je ne vois aucune preuve que la fusion a réussi et nameResults ne donne que des noms, pas des synonymes. Aucun message d'erreur n'apparaît.

Le code RxJS à http://reactivex.io/documentation/operators/merge.html utilise le code de la forme

const mergedResults = Rx.Observable.merge(nameResults, synonymResults); 

mais lorsque je tente un code tel que j'obtiens une erreur signalée:

Impossible de trouver le nom 'Rx'

Donc mes questions sont:

Comment peut-on utiliser RxJS merg e ici?

Y a-t-il une autre approche utilisant RxJS qui soit plus appropriée?

Existe-t-il une autre approche au niveau de la classe Hero qui fonctionnera, par exemple en calculant namePlusSynonym sans avoir à ajouter namePlusSynonym à chaque ligne du tableau Hero? ADDENDUM: suggestions ici et code http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-merge J'ai créé une nouvelle version de hero-search.service.ts avec les sorties de la console.

import { Injectable } from '@angular/core'; 
import { Http } from '@angular/http'; 

import { Observable } from 'rxjs/Observable'; 
import 'rxjs/add/operator/map'; 
import 'rxjs/add/operator/merge'; 

import { Hero } from './hero'; 

@Injectable() 
export class HeroSearchService { 

    constructor(private http: Http) {} 

    search(term: string): Observable<Hero[]> { 
    const nameResults = this.http.get(`api/heroes/?name=${term}`); 
    nameResults.subscribe(nameData => console.log('nameData', nameData)); 
    const synonymResults = this.http.get(`api/heroes/?synonym=${term}`); 
    synonymResults.subscribe(synonymData => console.log('synonymData', synonymData)); 
    const combinedResults = nameResults.merge(synonymResults); 
    combinedResults.subscribe(combinedData => console.log('combinedData', combinedData)); 
    return combinedResults.map(response => response.json().data as Hero[]); 
    } 
} 

Mais il n'affiche que synonymResults, pas nameResults. Les deux noms nameResults et synonymResults me semblent OK dans la sortie de la console, mais combinedResults n'a que des données et l'URL pour les résultats des synonymes et ne semble pas affecté par la méthode de fusion.J'ai également essayé avec la concaténation, avec des résultats similaires.

Il semble que je ne suis pas combiner au bon moment, mais la combinaison à un stade différent donne le même résultat, montrant seulement synonymes:

search(term: string): Observable<Hero[]> { 
    const nameResults = this.http.get(`api/heroes/?name=${term}`).map(response => response.json().data as Hero[]); 
    nameResults.subscribe(nameData => console.log('nameData', nameData)); 
    const synonymResults = this.http.get(`api/heroes/?synonym=${term}`).map(response => response.json().data as Hero[]); 
    synonymResults.subscribe(synonymData => console.log('synonymData', synonymData)); 
    const combinedResults = nameResults.merge(synonymResults); 
    combinedResults.subscribe(combinedData => console.log('combinedData', combinedData)); 
    return combinedResults; 
    } 
+0

Il semble un peu inhabituel d'utiliser deux requêtes HTTP, puis de les fusionner côté client. Normalement, je m'attendrais à voir quelque chose comme 'api/heroes? Synonym = $ {synonym} & name = $ {name}'. Cependant, je pense qu'une chose qui vous manque est 'nameResults = nameResults.merge (synonymResults);' Vous ignorez le retour de fusion qui est l'observable fusionnée. – Pace

+0

Ou mieux encore, 'let combinedResults = nameResults.merge (synonymResults)', puis retournez 'combinedResults.map (...)' – Pace

+0

Changement des deux dernières lignes en 'const combinéResults = nameResults.fusionner (synonymResults); return combinedResults.map (réponse => response.json(). Données comme Hero []); 'aboutit au fonctionnement synonyme mais pas au nom. –

Répondre

0

Pour l'opérateur de fusion fonctionne, vous devez importer observable et fusion de rxjs, tout comme il est dans le plunker:

import { Observable } from 'rxjs/Observable'; 
import 'rxjs/add/operator/merge'; 

Comment peut-on utiliser rxJS fusionner ici?

Je pense que votre principal malentendu est ici que l'appel merge() sur un rendement observable une nouvelle fusionnée observable. Comme rxjs fonctions sont pure, appeler ici ne mute pas namedResults

nameResults.merge(synonymResults); 

Au lieu de cela, vous devez saisir les résultats dans une nouvelle constante

const combinedResults = nameResults.merge(synonymResults); 
return combinedResults; 

Y at-il une autre approche utilisant RxJS qui est plus adapté?

Voilà comment je résolu le problème (plunker here) pour le faire fonctionner sans changer l'élément angulaire existant, changeant seulement le hero-search.service.ts. Vous pourriez rendre cela plus beau en enchaînant les opérateurs, mais je pense que voir des noms de variables utiles avec des types à chaque étape est très utile pour l'apprentissage. Vous pouvez mettre un subscribe() & console.log() sur l'un des observables pour tester pour voir à quoi la valeur ressemble réellement.

search(term: string): Observable<Hero[]> { 
    // get the response observable from requesting the backend  
    const nameResults: Response = this.http.get(`api/heroes/?name=${term}`); 
    // boil that response down to just the data array we care about 
    const nameResultsArr: Observable<Hero[]> = nameResults.map(res => res.json().data); 
    // do the same for the synonym request 
    const synonymResults: Response = this.http.get(`api/heroes/?synonym=${term}`); 
    const synonymResultsArr: Observable<Hero[]> = synonymResults.map(res => res.json().data); 

    // combine the two observable arrays into a stream of arrays 
    const combinedHeroListObservables: Observable<Hero[]> = 
    nameResultsArr.merge(synonymResultsArr); 
    // concat the observable arrays onto each other 
    const concatenatedArrays: Observable<Hero[]> = 
    combinedHeroListObservables.reduce((accumulator, current) => accumulator.concat(current)); 

    // return the combined array 
    return concatenatedArrays; 
} 

mais lorsque je tente un code tel que j'obtiens une erreur signalée: Cannot find name 'Rx'

Si vous ne se soucient pas inutilement l'importation des choses que vous n'utilisez pas, vous pouvez obtenir ce Travailler avec

import * as Rx from 'rxjs/Rx'; 

Mais, il est recommandé de ne importer que les opérateurs/types dont vous avez besoin, comme dans l'exemple Plunker. PS: Comme certains l'ont dit dans les commentaires, il serait bon d'avoir une route de repos qui a déjà retourné les deux résultats combinés afin que le client n'ait pas à faire deux allers-retours au serveur. Mais, vous ne pouvez pas vraiment faire cela parce que vous utilisez l'exemple de l'équipe Angular.

+0

Merci pour la réponse détaillée. Cela fonctionne en effet dans Plunker, mais quand je le mets dans VSCode, j'obtiens 8 erreurs et le programme ne démarre pas. nameResults est marqué avec le type 'Observable ' n'est pas assignable au type 'Réponse'. La propriété 'body' est manquante dans le type 'Observable '. map est marqué avec la propriété 'map' n'existe pas sur le type 'Response'. const combinedHeroListObservables: Observable []> = nameResultsArr.merge (synonymResultsArr); –

+0

Et combinéHeroListObservables est marqué avec Type 'Observable ' n'est pas assignable à type 'Observable []>'. Le type 'Héros []' n'est pas assignable au type 'Observable []'. Le type 'Héros' n'est pas assignable au type 'Observable '. La propriété '_isScalar' est manquante dans le type 'Hero'. –

+0

Et je ne sais pas si l'importation 'rxjs/add/observable/from'; est nécessaire ou laissé de quelque chose d'autre. –