Download files via POST request in AngularJs

There are times when you need to download file but the download is initiated via POST request, because the request contains too much parameters to fit into limited GET request. In my case I needed to generate ZIP file.

First you need to define custom method on your $resource which will handle the download method, like in the example below. Have a look at the transformResponse method. It creates a Blob from the server response (binary representation of the Zip file) and tries to extracts the file name from response headers.

Then you need to define method on controller. User clicks button “Download ZIP”, your controller calls MyResource.download(). That returns a (transformed) promise containing the Blob. Once you have the Blob, you call saveAs to prompt “save file dialog” in browser.

SaveAs method is part of HTML5 spec and a polyfill is available here.

//Written in Typescript

Module.factory("MyResource", ["$resource", ($resource: ng.resource.IResourceService)
      => $resource(FinaDb.apiUrlBase + "FinanceManager".toLowerCase() + "/:Id", { Id: "@Id" },
          {
              //Define custom action on resource
              //Inspired by http://notjoshmiller.com/server-side-pdf-generation-and-angular-resource/
              download: {
                  method: 'POST',
                  url: 'url-of-server-side-method',
                  headers: {
                      accept: 'application/zip' //or whatever you need
                  },
                  responseType: 'arraybuffer',
                  cache: false,
                  transformResponse: (data, headers) => {
                      var zip = null;
                      if (data) {
                          zip = new Blob([data], {
                              type: 'application/zip' //or whatever you need, should match the 'accept headers' above
                          });
                      }

                      //server should sent content-disposition header
                      var fileName: string = getFileNameFromHeader(headers('content-disposition'));
                      var result = {
                          blob: zip,
                          fileName: fileName
                      };

                      return {
                          response: result
                      };
                  }
              }

          })
  ]);
  
  function getFileNameFromHeader(header: string): string {
      if (!header) return null;

      var result: string = header.split(";")[1].trim().split("=")[1];

      return result.replace(/"/g, '');
  }

Now define method on ng controller like that:

//Define method download() in your ng controller

$scope.download = () => {
      //Indicates that download is in progress
      $scope.isDownloading = true;

      //define parameters
      var params = {
          Idies: [] //List of idies of entities of whatever
      };

      return MyResource.download(params).$promise.then((data: any) => {
              //using saveAs.js (part of upcoming HTML5 API, but so far a polyfill)
              var blob = data.response.blob;

              var fileName: string = data.response.fileName || 'document.zip';
              
              //SaveAs is available at saveAs.js from http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js
              (<any>$window).saveAs(blob, fileName);
          })
          .finally(() => {
             $scope.isDownloading = false;
      });
  }