简体   繁体   English

Angular Observable 需要先完成

[英]Angular Observable need to complete first

@Input() list: string[];

ngOnInit() : void {
   this.valueMap = new Map<any, any>();
   this.getDataFromService();
   this.buildContainer();
}

private getDataFromService(): void {
  this.list.forEach(value-> {
      this.fetchService(value).subscribe(data ->{
          this.valueMap.set(value,data);
          }
       )
   })
}

private buildContainer(): void {
   console.log(this.valueMap.size); // shows always 0 even when service returns the data 
 }

Now thing is that I have to use this valueMap in the method buidlContainer() , hence I need to get the complete map first from service.现在的事情是我必须在方法buidlContainer()中使用这个valueMap ,因此我需要首先从服务中获取完整的 map。 And when I use that map in buildConatainer() it shows me undefined.当我在 buildConatainer() 中使用 map 时,它显示我未定义。

I understood that this is some async call issue.我知道这是一些异步调用问题。 Also, I can not call method buildContainer() for each value in.subscribe() as that would not be better idea.此外,我不能为 in.subscribe() 中的每个值调用方法 buildContainer(),因为这不是更好的主意。 So, I have to compute the Map first before processing that further..所以,在进一步处理之前,我必须先计算 Map。

Any help is appreciated and i can not modify the service from returning Observable to return Promise感谢您提供任何帮助,我无法将服务从返回 Observable 修改为返回 Promise

i want to do as below我想做如下

private buildContainer(): void {
   for([key,data] of this.valueMap) {
      -----some code----
    }
 }

Simply use forkJoin to wait for all observables to finish.只需使用forkJoin等待所有可观察对象完成。 forkJoin will bundle all observables into one. forkJoin将把所有的 observable 打包成一个。 This observable can be returned and inside ngOnInit you subscribe to it and call buildContainer .这个 observable 可以返回并在ngOnInit订阅它并调用buildContainer

 @Input() list: string[];

  ngOnInit(): void {
    this.valueMap = new Map<any, any>();
    this.getDataFromService().subscribe(_ => this.buildContainer());
  }

  private getDataFromService(): Observable<any> {
    return forkJoin(
      this.list.map(value =>
        this.fetchService(value).pipe(
          tap(data => this.valueMap.set(value, data))
        )
      )
    );
  }

  private buildContainer(): void {
    console.log(this.valueMap.size);
  }

The code would be even cleaner if you do not populate the valueMap inside the tap operator and instead use the result you get into your subscription function.如果您不在tap运算符中填充valueMap ,而是使用您在订阅 function 中获得的结果,代码会更加简洁。

This is one of the disadvantage (or advantage depending how you look at it) of introducing reactive paradigm to your code.这是将响应式范式引入代码的缺点之一(或优点取决于您如何看待它)。 A single point of reactive procedure would gradually creep into other parts of the code either partially or completely.反应过程的单点会逐渐部分或完全潜入代码的其他部分。

So I'd recommend you to make the valueMap reactive as well and make it respond based on the changes in getDataFromService() service.因此,我建议您也使valueMap具有响应性,并使其根据getDataFromService()服务中的更改进行响应。 One way would to make the valueMap variable a RxJS ReplaySubject with a buffer of 1. So it'll buffer (or hold) it's last value pushed to it and emit it immediately upon subscription.一种方法是将valueMap变量设置为 RxJS ReplaySubject ,缓冲区为 1。因此它将缓冲(或保持)它的最后一个值,并在订阅时立即发出它。

Try the following尝试以下

import { forkJoin, ReplaySubject, Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';

valueMap: ReplaySubject<Map<any, any>> = ReplaySubject<Map<any, any>>(1);
completed$ = new Subject<any>();

@Input() list: string[];

ngOnInit(): void {
  this.valueMap = new Map <any, any>();
  this.getDataFromService();
  this.buildContainer();
}

private getDataFromService(): void {
  forkJoin(this.list.map(value => this.fetchService(value))).pipe(
    takeUntil(this.completed$)             // <-- close subscription upon component `ngOnDestroy` hook trigger
  ).subscribe(
    data => {
      let map = new Map<any, any>();
      data.forEach((item, i) => {
        map.set(this.list[i], item);
      });
      this.valueMap.next(map);
    }
  );
}

private buildContainer(): void {
  this.valueMap.asObservable().pipe(
    take(1)               // <-- emit only the first notification and complete
  ).subscribe(
    data => {
      console.log(this.valueMap.size);
    }
  );
}

ngOnDestroy() {
  this.completed$.next();
  this.completed$.complete();
}

I've also used RxJS forkJoin() function to combine multiple observables and takeUntil operator close the subscription when the component is destroyed.我还使用 RxJS forkJoin() function 来组合多个可观察对象,并在组件被销毁时使用takeUntil运算符关闭订阅。

Working example: Stackblitz工作示例: Stackblitz

In the example, I've used jsonplaceholder API to mock some HTTP calls in the fetchService() function.在示例中,我使用jsonplaceholder API 来模拟fetchService() function 中的一些 HTTP 调用。

Note:笔记:

  1. The forkJoin() will emit only if all the observables complete.仅当所有可观察对象都完成时, forkJoin()才会发出。 If you're dealing with a stream of data, you will have to replace it with either combineLatest or zip based on your requirement.如果您正在处理 ZF7B44CFFAFD5C52223D5498196C8A2E7BZ 数据,则必须根据您的要求将其替换为combineLatestzip

  2. There is an obvious issue where if the buildContainer() function is called multiple times in quick successions, each will trigger individual subscriptions.有一个明显的问题,如果buildContainer() function 被快速连续调用多次,每个都会触发单独的订阅。

Remove buildContainer from ngOnInit and Call the method inside the subscribengOnInit 中删除 buildContainer调用subscribe中的方法

 private getDataFromService(): void {
    this.list.forEach((value, index) -> {
        this.fetchService(value).subscribe(data ->{
            this.valueMap.set(value,data);
                   }
         )
         if(this.list.length-1 == index){
          this.buildContainer(); // call here
         }
     })
  }

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

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