【问题标题】:How to merge 2 searches in Angular Heroes tutorial?如何在 Angular Heroes 教程中合并 2 个搜索?
【发布时间】:2018-02-15 09:30:40
【问题描述】:

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

使用 Angular 2 / 4 Tour of Heroes 教程说明了这种情况,该教程使用 Observable 来搜索英雄的“名称”值 (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 类中:

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

并为每个英雄添加同义词:

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。但是,我无法使其正常工作。我尝试了各种使用合并的方法,包括上面新版本的 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 数组中的每一行?

附录:按照此处的建议和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 对我来说都不错,但是 combineResults 只有数据和同义词结果的 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;
  }

【问题讨论】:

  • 使用两个HTTP请求然后在客户端合并它们似乎有点不寻常。通常我希望看到像api/heroes?synonym=${synonym}&amp;name=${name} 这样的东西。但是,我认为您缺少的一件事是 nameResults = nameResults.merge(synonymResults); 您忽略了合并的返回,这是合并后的 observable。
  • 或者更好,let combinedResults = nameResults.merge(synonymResults) 然后返回 combinedResults.map(...)
  • 将最后两行更改为:const combinedResults = nameResults.merge(synonymResults); return combinedResults.map(response =&gt; response.json().data as Hero[]); 导致同义词有效,但名称无效。
  • 它应该发出两次,每次 HTTP 调用一次。您的第二个结果是否有可能覆盖您的第一个?如果您想将两个结果合并到一个列表中,您将需要类似...let combinedResults = nameResults.merge(synonymResults).reduce((a, b) =&gt; a.concat(b));
  • 建议的行在 IDE 中被标记为错误:“响应”类型上不存在属性“concat”。我不熟悉这些操作,所以我很难排除故障。

标签: angular merge get rxjs reactivex


【解决方案1】:

要让合并运算符工作,你需要从 rxjs 导入 Observable 并合并,就像在 plunker 中一样:

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

如何在这里使用 RxJS 合并?

我认为您在这里的主要误解是在 observable 上调用 merge() 会返回一个新合并的 observable。由于 rxjs 函数是pure,所以在这里调用它不会改变namedResults

nameResults.merge(synonymResults);

相反,您应该在一个新常量中捕获结果

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

还有其他更适合使用 RxJS 的方法吗?

这就是我解决问题 (plunker here) 以使其在不更改现有角度组件的情况下工作的方法,只更改 hero-search.service.ts。您可以通过将运算符链接在一起使这看起来更好,但我认为在每个阶段看到有用的变量名称和类型对学习非常有帮助。您可以在任何 observables 上放置一个中间 subscribe()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:就像某些人在 cmets 中所说的那样,最好有一个已经返回两个结果的休息路由,这样客户端就不必两次往返服务器。但是,您不能真正做到这一点,因为您使用的是 Angular 团队的示例。

【讨论】:

  • 感谢您的详细回复。这确实在 Plunker 中运行,但是当我将它放入 VSCode 时,我得到 8 个错误并且程序无法启动。 nameResults 被标记为“Observable”类型,不可分配给“Response”类型。 'Observable' 类型中缺少属性 'body'。地图被标记为“响应”类型上不存在属性“地图”。 const combinedHeroListObservables: Observable[]> = nameResultsArr.merge(synonymResultsArr);
  • 并且combinedHeroListObservables 被标记为类型'Observable' 不能分配给类型'Observable[]>'。类型 'Hero[]' 不能分配给类型 'Observable[]'。类型 'Hero' 不能分配给类型 'Observable'。 “英雄”类型中缺少属性“_isScalar”。
  • 而且不知道import 'rxjs/add/observable/from';需要或从其他东西遗留下来。
  • 你的 package.json 是什么?当我从 plunker 下载时,我没有得到一个。看起来这个项目的日期是在 angular 切换到 webpack 之前,并且仍在使用 systemjs ..
  • 我用正确的类型和更简单的解决方案更新了答案/plunker。 Observable.from() 是我在测试代码中对可观察数组进行硬编码留下的 const nameResultsArr: Observable&lt;Hero[]&gt; = Observable.of([{id: 1, name: 'Hero 1', synonym: 'Other Name 1'}]);
猜你喜欢
  • 2016-10-20
  • 2018-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-21
  • 1970-01-01
  • 2021-02-26
  • 1970-01-01
相关资源
最近更新 更多