【问题标题】:Transforming an object array returned from observable into array of another type将 observable 返回的对象数组转换为另一种类型的数组
【发布时间】:2019-01-11 17:17:41
【问题描述】:

情况是这样的:我有“任务服务”返回一个可观察的任务数组:

  getMissions(): Observable<Mission[]> {
  return this.http.get<Mission[]>(this.MissionUrl).pipe(
    tap(_ => this.log('Fetched missions.')),
    catchError(this.handleError('getMissions: ', []))
  );

以及组件内这个完美运行的订阅者功能:

export class TreeViewerComponent implements OnInit {
  missions: Mission[];
  Nodes: Node[]=[];
  Edges: Edge[];

  constructor(private missionService: MissionService,
              private messageService: MessageService) { }

  ngOnInit() {
      this.getNodes();

  }

    getNodes(): void {
      this.missionService.getMissions()
      .subscribe(missions => {
          this.missions = missions;
          this.doThis();
        })
}

    doThis(): void{
      console.log('tree-viewer component1: '+this.missions);
      for (var i=0;i<this.missions.length;i++)
      {
        var tempNode= {id: this.missions[i].key, label: this.missions[i].Title};
        console.log(tempNode);
        this.Nodes.push(tempNode);
      }
      this.getEdges();
      this.wrapData();
    }

问题是,它不是一个很好的关注点分离。查看器组件不应该处理所有这些业务逻辑。我想将类型转换从 Mission 类型(从远程服务器返回)中分离出来,以便将 Node 类型输入到另一个服务中。

如何异步执行?

实施后修改建议:

我还是做错了什么。正如建议的那样,我在 treeHandlerService 中有两个版本的 getNodes 方法。第一个是@fmontes 建议的实施,确实有效:

  getNodes(): void
  {
    this.missionService
        .getMissions()
        .pipe(
            flatMap((missions: Mission[]) => missions), // this will pass each mission to the map
            map((mission: Mission) => { // this will turn each Mission into a Node
                return new Node(mission.key, mission.Title) ;
            }),
            toArray() // this will group the nodes into an array of nodes
        )
        .subscribe((nodes: Node[]) => {
            this.Nodes = nodes;
            this.log('nodes: '+this.Nodes[0].label);//the log shows that it is working!
        });
  }

唉,当我去组件读取treeHandlerService.Nodes(异步更新的数组)时,它仍然是未定义的(意味着异步活动还没有完成)。

另一个版本是缩短的,我试图立即返回值:

  getNodes(): Observable<Node[]> {
    return this.missionService.getMissions().pipe(
      map((mission: Mission) => { // this will turn each Mission into a Node
          return new Node(mission.key, mission.Title) ;
      }),
      toArray()
    )
  };

但它甚至无法编译!我收到以下错误:

ERROR in src/app/tree-handler.service.ts(39,7): error TS2345: Argument of type 'OperatorFunction<Mission, Node>' is not assignable to parameter of type 'OperatorFunction<Mission[], Node>'.

类型“任务”不能分配给类型“任务[]”。 “任务”类型中缺少属性“长度”。

请大家帮忙...

【问题讨论】:

  • 使用 rxjs map/switchmap ...

标签: angular rxjs angular-services angular2-observables


【解决方案1】:

你可以这样做:

// Don't forget to import the operators
import {map, flatMap, toArray} from 'rxjs/operators';


this.missionService
    .getMissions()
    .pipe(
        flatMap((missions: Mission[]) => missions), // this will pass each mission to the map
        map((mission: Mission) => { // this will turn each Mission into a Node
            return { id: mission.key, label: mission.Title };
        }),
        toArray() // this will group the nodes into an array of nodes
    )
    .subscribe((nodes: Node[]) => {
        this.nodes = nodes;
    });

【讨论】:

  • Tnx @fmontes 为您快速解答!你的代码建议确实比我的好,但看起来我还是应该把它放在组件本身上。我最初的设计是创建将执行此操作的 TreeHandlerService,然后组件可以调用它而不知道是否存在除 Node 和 Edge 之外的任何类型的对象。这是一个好的方向吗?你能告诉我新服务的样子吗?
  • 您可以将其移至服务本身,然后创建返回 Node[] 的方法
【解决方案2】:

我认为可以进一步简化如下:

import { map, catchError } from 'rxjs/operators';

export class MissionService {

  ...

  getMissions(): Observable < Mission[] > {
    return this.http.get < Mission[] > (this.MissionUrl).pipe(
      map(missions => missions.map(mission => ({ 
        id: mission.key,
        label: mission.Title }) 
      )),
      catchError(this.handleError('getMissions: ', []))
    );
  }

  ...

}

在这里,我没有额外使用 flatMaptoArray 运算符,而是简单地使用 Rxjs 的 map 运算符和数组上的 map 方法来完成必要的工作。

然后在你的组件类中:

export class TreeViewerComponent implements OnInit {
  missions: Mission[];
  Nodes: Node[] = [];
  Edges: Edge[];

  constructor(
    private missionService: MissionService,
    private messageService: MessageService
  ) {}

  ...

  getNodes(): void {
    this.missionService.getMissions()
      .subscribe(missions => {
        this.missions = missions;
      });
  }

  ...

}

【讨论】:

  • Tnx 伙计们,您的回答确实提高了代码的简洁性和可读性。我仍然希望 TreeViewerComponent 不必处理 Mission 对象,并且能够完全呈现其他对象。所以我希望另一个服务 TreeHandlerService 会为他处理业务逻辑。 SiddAjmera 您的实现仍然使组件知道 Mission 对象。 @fmontes 如果我返回 Node[],它是否已经加载了数据?当我之前尝试过此操作时,看起来返回发生在异步操作完成之前,因此结果未定义。
  • 是的...我认为您需要在这里创建一个服务来获取节点,而该服务是使用 MissionService 来获取任务的服务...或者取决于您数据以及如何相关,您可以在 MissionServices 中创建一个新方法,例如 getMissionsAsNodes 并将我注释的代码移动到该方法中,然后在您的组件中调用该新方法。但就像我说的,这取决于你的数据是如何相关或结构化的。
  • 仍然无法正常工作:-(。我已经使用所有附加数据编辑了原始问题。
  • @Angular7Starter,如果你能提供一个Minimal, Complete, and Verifiable example,那就太好了。您可以使用StackBlitz 创建一个。
  • 您在地图前缺少flatMap((missions: Mission[]) =&gt; missions)。这会将一个任务传递给map,现在您正在传递一系列任务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-23
  • 1970-01-01
  • 2010-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多