【问题标题】:Angular subscribe within subscribe: data doesn't load at the same time within view订阅中的角度订阅:数据不会在视图中同时加载
【发布时间】:2019-12-09 22:00:58
【问题描述】:

我知道在 subscribe 中调用 subscribe 是不好的做法,但我不知道如何在我的特殊情况下以不同的方式处理它。 现在的代码可以工作,但我的问题是,如果我每秒更新我的网站,则首先加载表格的一部分,然后加载其他部分(我订阅中的订阅内容)。

我有一个服务,其中包含一个函数,该函数返回不同资产的文件列表的 Observable。 在该函数中,我通过调用另一个服务来请求每个资产的文件列表,并且该服务返回 observables。 然后我遍历该列表的元素并构建我的数据结构以便稍后返回它们(AssetFilesTableItems)。 有些文件可以是 zip 文件,我想通过订阅另一个服务(extractZipService)来获取这些文件的内容。为了能够获得正确的数据,我需要通过请求文件列表获得的文件的名称。然后我将 zip 内容的一些数据添加到我的 AssetFilesTableItems 并在最后返回所有内容。

该函数的代码如下:

  getAssetfilesData(assetIds: Array<string>, filter: RegExp, showConfig: boolean): Observable<AssetFilesTableItem[][]> {
    const data = assetIds.map(assetId => {
      // for each assetId
      return this.fileService.getFileList(assetId)
        .pipe(
          map((datasets: any) => {
            const result: AssetFilesTableItem[] = [];

            // iterate over each element
            datasets.forEach((element: AssetFilesTableItem) => {

              // apply regex filter to filename
              if (filter.test(element.name)) {
                this.logger.debug(`Filter ${filter} matches for element: ${element.name}`);

                // build up AssetFilesTableItem
                const assetFilesItem: AssetFilesTableItem = {
                  name: element.name,
                  type: element.type,
                  asset: assetId
                };

                // save all keys of AssetFilesTableItem
                const assetFilesItemKeys = Object.keys(assetFilesItem);

                // if file is of type ZIP, extract 'config.json' from it if available
                if (showConfig && element.type.includes('zip')) {
                  this.extractZipService.getJSONfromZip(assetId, element.name, 'config.json')
                    .subscribe((configJson: any) => {
                      const jsonContent = JSON.parse(configJson);
                      const entries = Object.entries(jsonContent);
                      entries.forEach((entry: any) => {
                        const key = entry[0];
                        const value =  entry[1];
                        // only add new keys to AssetFilesTableItem
                        if (!assetFilesItemKeys.includes(key)) {
                          assetFilesItem[key] = value;
                        } else {
                          this.logger.error(`Key '${key}' of config.json is already in use and will not be displayed.`);
                        }
                      });
                    });
                }
                result.push(assetFilesItem);
              }
            });

            return result;
          }));
    });

    // return combined result of each assetId request
    return forkJoin(data);
  }
}

我在组件中使用以下代码更新表:

  getValuesPeriodically(updateInterval: number) {
    this.pollingSubscription = interval(updateInterval)
      .subscribe(() => {
        this.getAssetfilesFromService();
      }
    );
  }

getAssetfilesFromService() {
    this.assetfilesService.getAssetfilesData(this.assetIds, this.filterRegEx, this.showConfig)
    .subscribe((assetFilesTables: any) => {
      this.assetFilesData = [].concat.apply([], assetFilesTables);
    });
  }

编辑:我尝试了 ForkJoin,但据我了解,它用于并行执行更多请求。我的 extractZipService 虽然取决于我从 fileService 获得的结果。我最后也有一个 forkJoin,它应该结合我对不同资产的所有 fileList 请求。我不明白为什么我的视图没有立即加载。

编辑:问题似乎是在我的 fileService 订阅的 forEach 中订阅了 extractZipService。它似乎在 fileService 订阅之后完成。我已经尝试了很多东西,比如 SwitchMap、mergeMap 和这里建议的解决方案,但没有运气。我确信有可能让它以某种方式工作,但我的想法已经不多了。任何帮助,将不胜感激!

【问题讨论】:

  • 在接受的答案中查看 forkJoin 以并行运行请求
  • 据我了解,forkJoin 用于并行执行更多请求,但我的 extractZipService 取决于我从 fileService 获得的结果。我最后也有一个 forkJoin,它可以结合我对不同资产的所有 fileList 请求。我不明白为什么我的视图没有立即加载。
  • 好的,当时有点仓促,没有正确阅读。对不起,我的错。我重新打开了问题:)

标签: javascript angular typescript rxjs


【解决方案1】:

您在 for 循环中调用 this.extractZipService.getJSON。所以这个方法被称为 asynch 并且你在 map 中的函数不会等待结果。当结果确实出现时,因为您的项目是相同的,在您看来,它们会被刷新。

要解决这个问题,您需要从 this.extractZipService.getJSON 返回并映射结果,这将为您提供结果集合,然后您对结果执行 forkJoin(不确定为什么需要 forkjoin,因为只有对象而不是 API你需要打电话)

this.logger.debug(`ConfigJson found for file '${element.name}': ${configJson}`);
const jsonContent = JSON.parse(configJson);
const entries = Object.entries(jsonContent);
entries.forEach((entry: any) => {
 // code
});

完整的代码应该看起来类似:-

getAssetfilesData(assetIds: Array<string>, filter: RegExp, showConfig: boolean): Observable<AssetFilesTableItem[][]> {
    const data = assetIds.map(assetId => {
      // for each assetId
      return this.fileService.getFileList(assetId)
        .pipe(
          map((datasets: any) => {

            // iterate over each element
            datasets.forEach((element: AssetFilesTableItem) => {

              return this.extractZipService.getJSONfromZip(assetId, element.name, 
              'config.json')
            });               
           })).map((configJson: any) => {
              // collect your results and return from here        
              // return result
         });;    
     });

    // return combined result of each assetId request
    return forkJoin(data);
  }
}

我创建了一个 Stackblitz(https://stackblitz.com/edit/nested-subscribe-solution),它们的工作方式相同。您需要使用 concatMap 和 forkJoin 来获取所有结果。 希望这会有所帮助。

【讨论】:

  • 谢谢,我现在知道问题出在哪里,但我仍然坚持要解决它。我尝试了您的解决方案,但它不起作用,因为我仅在文件类型为 zip 时才调用 extractZipService,有时 zip 服务会返回数据,有时则不会(当该 zip 文件中没有 config.json 时)。这是预期的行为。实际上,getJSONFromZip 方法返回一个 Observable。也许还有另一种方法可以让map里面的函数等待ZipService的结果。我想我必须在星期一更深入地研究 rxjs..
  • @Manuela 创建了 Stackblitz。希望这会有所帮助。
猜你喜欢
  • 1970-01-01
  • 2021-01-10
  • 1970-01-01
  • 2020-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-15
  • 1970-01-01
相关资源
最近更新 更多