0

I am trying to perform operations in a .subscribe() that is within another .subscribe(): collecting files so they can all be zipped after that.

I am using JSZip to handle the 'zipping' part of the code.

I have already tried using .map() instead of iterating through the for loop. I read here (Is it good way to call subscribe inside subscribe?) that I could solve the problem using .flatMap() because my 2nd Observable (downloading files), depends on the results of the first one (getting the list of files), but I still couldn't figure it out...

I currently have an implementation that is working, but it uses setTimeout() so it 'waits' for the files to be downloaded so I can then zip them, but I believe that is not the best approach.

    this.downloadingZipFile = true;
    let zip = new JSZip();
    let tableTemplateCollection = '';
    let givenName = this.patientGivenName;

    let templateIndex = zipTemplate.templateIndex; 

    console.log('TOTAL FILES COUNT: ' + this.totalFiles);
    this.fileViewerDataService.getFileNamesList(this.patient.getPatientQueryParams(), this.totalFiles, 0)
      .subscribe((data) => {
        let dataLength = Object.keys(data).length;
        for (let i = 0; i < dataLength; i++) {
          console.log('THIS IS THE ID: ' + data[i]['indexId']);
          this.fileViewerDataService.getFileViewByIdAsBlob(data[i]['indexId']).subscribe(patientFile => {
            console.log(`Saving... ${data[i]['indexId']}`);
            if ((data[i]['name']).includes('immunization')) {
              console.log('THIS IS THE PATIENT FILE: ' + patientFile);
              zip.folder('immunization').file(`${data[i]['name']}.html`, patientFile);
              tableTemplateCollection += this.indexCreation(data[i]['name']);
            } else if ((data[i]['name']).includes('laboratory')) {
              console.log('THIS IS THE PATIENT FILE: ' + patientFile);
              zip.folder('laboratory').file(`${data[i]['name']}.html`, patientFile);
              tableTemplateCollection += this.indexCreation(data[i]['name']);
            } else {
              console.log('THIS IS THE PATIENT FILE: ' + patientFile);
              zip.folder('medication').file(`${data[i]['name']}.html`, patientFile);
              tableTemplateCollection += this.indexCreation(data[i]['name']);
            }
            this.downloadingZipFile = false;
          });
        }
        setTimeout(function () {
          templateIndex = templateIndex.replace('##NAME##', givenName);
          tableTemplateCollection = templateIndex.replace('##FILES##', tableTemplateCollection);
          zip.file('index.html', tableTemplateCollection);
          zip.file('data.js', translateEnFr.data);
          zip.generateAsync({ type: "blob" }).then(function (patientFolders) {
            saveAs(patientFolders, "Report.zip");
          });
        }, 2500);
      },
        err => {
          window.alert(`${err.status}: Error downloading zip from API.`);
        })
  }
yagosansz
  • 47
  • 2
  • 8

1 Answers1

0

You are certainly right that setTimeout() is not the best approach. You could use promises like this:

this.fileViewerDataService.getFileNamesList(this.patient.getPatientQueryParams(), this.totalFiles, 0)
  .subscribe((data) => {
    let dataLength = Object.keys(data).length;
    let promises = [];
    for (let i = 0; i < dataLength; i++) {
      console.log('THIS IS THE ID: ' + data[i]['indexId']);
      promises.push(createPromise(data[i]));
    }
    Promise.all(promises).then(() => {
      templateIndex = templateIndex.replace('##NAME##', givenName);
      tableTemplateCollection = templateIndex.replace('##FILES##', tableTemplateCollection);
      zip.file('index.html', tableTemplateCollection);
      zip.file('data.js', translateEnFr.data);
      zip.generateAsync({ type: "blob" }).then((patientFolders) => saveAs(patientFolders, "Report.zip"));
    }).catch(err => {
      window.alert(`${ err.status }: Error downloading zip from API.`);
    });
  },
    err => {
      window.alert(`${ err.status }: Error downloading zip from API.`);
    });

createPromise() would look like this:

createPromise(data) {
  return new Promise((resolve, reject) => {
    this.fileViewerDataService.getFileViewByIdAsBlob(data['indexId']).subscribe(patientFile => {
      console.log(`Saving... ${ data['indexId'] }`);
      if ((data['name']).includes('immunization')) {
        console.log('THIS IS THE PATIENT FILE: ' + patientFile);
        zip.folder('immunization').file(`${ data['name'] }.html`, patientFile);
        tableTemplateCollection += this.indexCreation(data['name']);
      } else if ((data['name']).includes('laboratory')) {
        console.log('THIS IS THE PATIENT FILE: ' + patientFile);
        zip.folder('laboratory').file(`${ data['name'] }.html`, patientFile);
        tableTemplateCollection += this.indexCreation(data['name']);
      } else {
        console.log('THIS IS THE PATIENT FILE: ' + patientFile);
        zip.folder('medication').file(`${ data['name'] }.html`, patientFile);
        tableTemplateCollection += this.indexCreation(data['name']);
      }
      this.downloadingZipFile = false;
      resolve();
    }, error => reject(error));
  });
}
dockleryxk
  • 407
  • 2
  • 11
  • I implemented your solution and it worked like a charm! But, what if I wanted to have a solution that uses only Observables and had to implement createObservable(data), would it work the same way? I find observables really hard to understand =( – yagosansz Jul 02 '19 at 18:36
  • Glad it worked! You could, I just figured the promise would be more straightforward. You could use Observable.combineLatest instead of Promise.all (https://www.learnrxjs.io/operators/combination/combinelatest.html) by passing an array of observables in instead. This would require some tweaking though, I'm not sure if the logic would need to change. Give it a shot though if you are wanting a good exercise! – dockleryxk Jul 02 '19 at 20:13