Manuela
Manuela

Reputation: 137

Angular subscribe within subscribe: data doesn't load at the same time within view

I know it is bad practice to call subscribe within subscribe but I don't know how to handle it differently with my special case. The code as it is now works, but my problem is that if I update my website for example every second, parts of the table are loaded first and other parts are loaded afterwards (the content of the subscibe within my subscribe).

I have a service containing a function that returns an Observable of a list of files for different assets. Within that function I request the filelist for each asset by calling another service and this service returns observables. I then iterate over the elements of that list and build up my data structures to return them later on (AssetFilesTableItems). Some files can be zip files and I want to get the contents of those files by subscribing to another service (extractZipService). To be able to get that correct data I need the name of the file which I got by requesting the filelist. I then add some data of the zip contents to my AssetFilesTableItems and return everything at the end.

The code of that function is as follows:

  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);
  }
}

I update my table using the following code within my component:

  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);
    });
  }

Edit: I tried ForkJoin, but as far as I understandit is used for doing more requests in parallel. My extractZipService though depends on results that I get from my fileService. Also I have a forkJoin at the end already which should combine all of my fileList requests for different assets. I don't understand why my view is not loaded at once then.

EDIT: The problem seems to be the subscribe to the extractZipService within the forEach of my fileService subscribe. It seems to finish after the fileService Subscribe. I tried lots of things already, like SwitchMap, mergeMap and the solution suggested here, but no luck. I'm sure it's possible to make it work somehow but I'm running out of ideas. Any help would be appreciated!

Upvotes: 0

Views: 1411

Answers (1)

Sanket
Sanket

Reputation: 632

You are calling this.extractZipService.getJSON inside a for loop. So this method gets called asynch and your function inside map is not waiting for the results. When result does come as your items are same which is in your view they get refreshed.

To solve this you need to return from this.extractZipService.getJSON and map the results which will give you a collections of results and then you do forkJoin on results ( Not sure why you need to forkjoin as there are just the objects and not API's which you need to call )

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
});

complete code should look on similar lines :-

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);
  }
}

I have created a Stackblitz(https://stackblitz.com/edit/nested-subscribe-solution) which work along the same lines. You need to use concatMap and forkJoin for getting all the results. Hope this helps.

Upvotes: 1

Related Questions