angulartypescriptjszipfilesaver.js

Angular 11 - To add files zip using JSZip


I am trying to add several txt files to a zip file and download it locally.

For this, I am using the libraries: JSZip and FileSaver.

This is the "typescript" code, of the download button:

let zip = new JSZip();
zip.file("readme.txt", "Files required");
let txtFile = zip.folder("txt");
this.selectedItems?.forEach((item.name) => {
this.downloadService
.downloadFile(item.name)
.subscribe((response) => {
  let base64 = response.output.split(",");
  txtFile.file(item.name, base64[1], {base64: true});
});
});
zip.generateAsync({type:"blob"})
.then((content) => {
// see FileSaver.js
FileSaver.saveAs(content, this.fileZipName);
});

This code, downloads the zip file successfully, with a readme.txt file and an empty txt folder. The txt folder should contain the txt files obtained from the "downloadService" service.

Of course, removing the call to the service that returns the files, and putting a hardcoded base 64 file, if we correctly generate the zip with its txt folder and the corresponding files inside it, with the same content in all the TXT files, because is hardcoded.

let zip = new JSZip();
zip.file("readme.txt", "Files required");
let txtFile = zip.folder("txt");
this.selectedItems?.forEach((item) => {
txtFile.file(item.name, "VGVzdCBBbGltXzEw", {base64: true});
});
zip.generateAsync({type:"blob"})
.then((content) => {
// see FileSaver.js
FileSaver.saveAs(content, this.fileZipName);
});

I update the question, with the code I'm testing at the moment, this generates several zips depending on the files processed, in the last zip is adding it the files to the txt folder. But I need to generate a single zip file. FileSaver.saveAs, being inside the loop that recovers the files, will generate a zip for each file, and in the last zip, if it adds all the files that I need.

let zip = new JSZip();
zip.file("readme.txt", "Files required");
let txtFile = zip.folder("txt");
this.selectedItems?.forEach((item) => {
this.downloadService.downloadFile(item.name)
  .subscribe((response) => {
    let base64 = response.output.split(",");
    txtFile.file(item.name, base64[1], {base64: true});
    zip.generateAsync({type:"blob"})
      .then((content) => {
      // see FileSaver.js
      FileSaver.saveAs(content, this.fileZipName);
    });
  });
});

selectedItems: It is an array of objects that contains the files, the name property contains the name of the file.

I don't know how to call the "downloadFile" service so that for each file that exists, it makes the corresponding call, to obtain the base64 and add it to the zip.

Depending on the files that we select, if for example we have 2 files, the response of the service would be as follows:

Call one file:

{"errorMessages":[],"output": "data:text/plain;base64,VGVzdCBJbmZyYTEw"}

Call one file:

{"errorMessages":[],"output": "data:text/plain;base64,VGVzdCBJbmZyYTEy"}

The base64[1] variable contains the base64 code of the txt file.

In short, when I hardcode the base64 code it works fine and compresses the files with the same content, but if I try to get the base64 code dynamically by calling my "downloadFile" service, it doesn't add them to the zip in the txt folder and doesn't give any errors.

Thanks,


Solution

  • I haven't been able to use a forkjoin, but I with this condition works as I need:

    let zip = new JSZip();
    zip.file("readme.txt", "Files required");
    let txtFile = zip.folder("txt");
    let i = 0;
    this.selectedItems?.forEach((item) => {
    this.downloadService.downloadFile(item.name)
      .subscribe((response) => {
        let base64 = response.output.split(",");
        txtFile.file(item.name, base64[1], {base64: true});
        i++;
        if (i === this.selectedItems.length) { // I have added this condition
          zip.generateAsync({type:"blob"})
          .then((content) => {
            // see FileSaver.js
            FileSaver.saveAs(content, this.fileZipName);
          });
        }
      });
    

    });