简体   繁体   English

如何正确地将 RxJS observables 与 for 循环链接在一起?

[英]How to properly chain RxJS observables together with a for loop?

I have a projectService to retrieve projects.我有一个 projectService 来检索项目。 Each project in turn has methods to retrieve corresponding tags and modules.每个项目依次具有检索相应标签和模块的方法。

What i try to do is populate an instance property projects: Project[] with all corresponding tags and modules and when everything is populated call another method, which depends on the data.我尝试做的是用所有相应的标签和模块填充一个实例属性项目:Project[],当所有内容都被填充时调用另一个方法,这取决于数据。

I know my approach is not working because my map operation returns the projects before the observables in the for loop complete, but I have no idea how to do it properly.我知道我的方法不起作用,因为我的 map 操作会在 for 循环中的可观察对象完成之前返回项目,但我不知道如何正确执行。

class ProjectService {
  getAll(): Observable<Project[]> {...}
}
class Project {
  tagCollection: Tag[];
  moduleCollection: Module[];

  getTags(): Observable<Tag[]> {...}
  getModules(): Observable<Module[]> {...}
}
this.projectService
  .getAll()
  .pipe(
    map(projects => {
      for (const project of projects) {
        project.getTags().subscribe(tags => {
          project.tagCollection = tags;
        });
        project.getModules().subscribe(modules => {
          project.modules = modules;
        });
      }
      return projects;
    })
  )
  .subscribe(projects => {
    this.projects = projects;
    this.someOtherMethod();
  });

Update更新

I have tried both solutions and edited them to retain the project type and use a similar coding style.我已经尝试了这两种解决方案并对其进行了编辑以保留项目类型并使用类似的编码风格。 Both solutions work and seem to do the same thing in my project context.这两种解决方案都有效,并且在我的项目上下文中似乎做同样的事情。 But I'm unsure which solution is better and if the edits i made are breaking any reactive best practices.但我不确定哪种解决方案更好,以及我所做的编辑是否违反了任何反应性最佳实践。 Can someone elaborate on this?有人可以详细说明吗?

Solution 1 from Reqven来自 Reqven 的解决方案 1

this.projectService
  .getAll()
  .pipe(
    switchMap(projects =>
      combineLatest(
        projects.map(project =>
          combineLatest([project.getTags(), project.getModules()]).pipe(
            map(([tags, modules]) => {
              project.tagCollection = tags;
              project.modules = modules;
              return project;
            })
          )
        )
      )
    )
  )
  .subscribe(projects => {
    console.log(projects);
    this.projects = projects;
    this.someOtherMethod();
  });

Solution 2 from NateDev来自 NateDev 的解决方案 2

this.projectService
  .getAll()
  .pipe(
    mergeMap(projects => from(projects)),
    mergeMap(project => 
      combineLatest([project.getTags(), project.getModules()]).pipe(
        map(([tags, modules]) => {
          project.tagCollection = tags;
          project.modules = modules;
          return project;
        })
      )
    ),
    toArray()
  )
  .subscribe(projects => {
    console.log(projects);
    this.projects = projects;
    this.someOtherMethod();
  });

Would something like that work for you?这样的事情对你有用吗?
Might need some tweaking to fit your project, but it seems to be working.可能需要进行一些调整以适合您的项目,但它似乎正在运行。

this.projectService.getAll()
  .pipe(
    switchMap(projects => !projects.length
      ? of([])
      : combineLatest(
          projects.map(project => combineLatest([
            project.getTags(),
            project.getModules()
          ]).pipe(
            map(([tags, modules]) => ({tags, modules})),
            map(data => ({
              ...project,
              tagCollection: data.tags,
              moduleCollection: data.modules
            })),
        ))
    ))
  ).subscribe(projects => {
    this.projects = projects;
    this.someOtherMethod();
  });

There a many different ways to perform batch calls asynchronously.有许多不同的方法可以异步执行批处理调用。

So what I like to do is flatten my data as I believe it's easier to perform operations on a more flat structure.所以我喜欢做的是扁平化我的数据,因为我相信在更扁平的结构上执行操作更容易。 After I'm done performing operations on the objects I regroup/reassemble them using the toArray operator完成对对象的操作后,我使用toArray运算符重新组合/重新组合它们

If you have any question regarding the operators I used feel free to ask.如果您对我使用的运算符有任何疑问,请随时提问。 I highly recommend you to play around with the different rxjs transformation operators .我强烈建议您尝试使用不同的rxjs 转换运算符

Be aware my code below is definitely not the best solution, but it works fine!请注意我下面的代码绝对不是最好的解决方案,但它工作正常!

Hope it works for you!希望这对你有用! StackBlitz 堆栈闪电战

import { Component, Input, OnInit } from '@angular/core';
import { Observable, of, from, combineLatest } from 'rxjs'
import { delay, map, mergeMap,switchMap, toArray } from 'rxjs/operators';

@Component({
  selector: 'hello',
  template: `<h1>Hello {{name}}!</h1>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent implements OnInit {
  @Input() name: string;

  data: Partial<Project>[] =  [
    { name: 'foo'},
    { name: 'foo'}
  ]

  ngOnInit() : void {
    this.getProjects().pipe(
      mergeMap(projects => from(projects)),
      mergeMap((project) => {
        const tag$ = this.getTagsByProjectName(project.name);
        const module$ = this.getModuleByProjectName(project.name);
        return combineLatest([tag$,module$]).pipe(map(([tag,module]) => ({...project, tag, module})))
      }),
      toArray()
    ).subscribe({
      next: projects => {
        console.log(projects);
        this.name = JSON.stringify(projects);
      }
    })
  }

  getProjects() : Observable<Partial<Project>[]> {
    return of(this.data).pipe(
      delay(100)
    )
  }

  getTagsByProjectName(name: string){
    const tag = 'bar';
    return of(tag).pipe(delay(100));
  }

  getModuleByProjectName(name: string){
    const module = 'baz';
    return of(module).pipe(delay(100));
  }
}

interface Project {
  name: string; 
  tag: string; 
  module: string;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM