如何在Angular Heroes教程中合并2个搜索?

时间:2017-09-06 19:22:07

标签: angular merge get rxjs reactivex

我正在尝试在Angular / TypeScript中合并2个HTTP get搜索,但是我无法让它工作。

使用Angular 2/4 Tour of Heroes教程说明了这种情况,该教程使用Observable通过“name”值(https://angular.io/tutorial/toh-pt6#add-the-ability-to-search-by-name)搜索英雄。 hero-search.service.ts中的代码如下:

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[]);
  }
}

我需要为Hero类添加“synonym”的第二个字符串值:

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

并为每个Hero添加一个同义词:

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

然后我可以通过同义词代替hero:search.service.ts搜索:

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

我的问题是如何搜索名称和同义词,并返回带有名称或同义词的匹配项。

从ReactiveX文档中可以看出,答案是使用Merge运算符:http://reactivex.io/documentation/operators/merge.html。但是,我无法让它工作。我尝试了各种使用merge的方法,包括以下新版本的hero-search.service.ts代码:

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[]);
  }
}

无法正常运行的代码位于https://plnkr.co/edit/4mrL5ReQCSvuzOODixJU?p=preview

我没有看到任何证据表明合并成功,而nameResults只提供名称,而不是同义词。没有出现错误消息。

http://reactivex.io/documentation/operators/merge.html处的RxJS代码使用

形式的代码
const mergedResults = Rx.Observable.merge(nameResults, synonymResults);

但是当我尝试这样的代码时,我收到一个错误标记:

  

找不到名字'Rx'

所以我的问题是:

如何在这里使用RxJS合并?

是否有其他方法使用更合适的RxJS?

Hero类的其他方法是否可以运行,例如计算namePlusSynonym而不必将namePlusSynonym添加到Hero数组中的每一行?

ADDENDUM:此处提供以下建议和http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-merge处的代码我创建了一个带控制台输出的新版本的hero-search.service.ts。

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[]);
  }
}

但它只显示synonymResults,而不是nameResults。 nameResults和synonymResults在控制台输出中看起来都不错,但combinedResults只有数据和同义词结果的url,并且似乎不受merge方法的影响。

我也尝试过concat,结果相似。

似乎我没有在正确的阶段合并,但在不同的阶段合并会产生相同的结果,只显示同义词:

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

1 个答案:

答案 0 :(得分:0)

要使合并运算符起作用,您需要导入Observable并从rxjs合并,就像它在plunker中一样:

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

如何在这里使用RxJS合并?

我认为你在这里的主要误解是在一个observable上调用merge()会返回一个 new merged observable 。由于rxjs函数是pure,因此在此处调用它不会改变namedResults

nameResults.merge(synonymResults);

相反,您应该以新的常量

捕获结果
const combinedResults = nameResults.merge(synonymResults);
return combinedResults;
  

是否有其他方法使用更合适的RxJS?

这就是我解决问题的方法(plunker here),让它在不改变现有角度组件的情况下工作,只改变hero-search.service.ts。通过将操作符链接在一起可以使这看起来更好看,但我认为在每个阶段看到有用的变量名称与类型对学习非常有帮助。你可以放一个中级subscribe()&amp; console.log()在任何可观察的观察者身上进行测试,以了解该值的实际情况。

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

但是当我尝试这样的代码时,我收到一个错误标记:   Cannot find name 'Rx'

如果您不在乎不必要地导入您未使用的内容,则可以使用

import * as Rx from 'rxjs/Rx';

但是,最好只导入所需的运算符/类型,如plunker示例所示。

PS:正如评论中所说的那样,最好有一个休息路线,它已经结合了两个结果,所以客户不必两次往返服务器。但是,你可以做到这一点,因为你正在使用Angular团队的例子。