AngularJS $http-post - convert binary to excel file and download

JsonExcelAngularjsHttp Post

Json Problem Overview


I've created an application in Angular JS for downloading an Excel workbook through $http post.

In the below code I'm passing the information in the form of JSON , and send it to the server REST web service (java) through an angular $http post. The web service uses the information from the JSON and produces an Excel workbook. In the response within the success body of $http post, I'm getting binary data within that data variable, but don't know how to convert it and download as an Excel file.

Can anyone please tell me some solution for this for converting the binary to Excel file and download?

My code is as given below:

$http({
        url: 'myweb.com/myrestService',
        method: "POST",
        data: json, //this is your json data string
        headers: {
           'Content-type': 'application/json'
        }
    }).success(function (data, status, headers, config) {
       
        // Here i'm getting excel sheet binary datas in 'data' 

    }).error(function (data, status, headers, config) {
       
    });

Json Solutions


Solution 1 - Json

Just noticed you can't use it because of IE8/9 but I'll push submit anyway... maybe someone finds it useful

This can actually be done through the browser, using blob. Notice the responseType and the code in the success promise.

$http({
	url: 'your/webservice',
	method: "POST",
	data: json, //this is your json data string
	headers: {
	   'Content-type': 'application/json'
	},
	responseType: 'arraybuffer'
}).success(function (data, status, headers, config) {
	var blob = new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
	var objectUrl = URL.createObjectURL(blob);
	window.open(objectUrl);
}).error(function (data, status, headers, config) {
	//upload failed
});
There are some problems with it though like:
  1. It doesn't support IE 8 and 9:
  2. It opens a pop up window to open the objectUrl which people might have blocked
  3. Generates weird filenames
It did work!

blob The server side code in PHP I tested this with looks like this. I'm sure you can set similar headers in Java:

$file = "file.xlsx";
header('Content-disposition: attachment; filename='.$file);
header('Content-Length: ' . filesize($file));
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
header('Pragma: public');
echo json_encode(readfile($file));
Edit 20.04.2016

Browsers are making it harder to save data this way. One good option is to use filesaver.js. It provides a cross browser implementation for saveAs, and it should replace some of the code in the success promise above.

Solution 2 - Json

This is how you do it:

  1. Forget IE8/IE9, it is not worth the effort and does not pay the money back.
  2. You need to use the right HTTP header,use Accept to 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' and also you need to put responseType to 'arraybuffer'(ArrayBuffer but set with lowercase).
  3. HTML5 saveAs is used to save the actual data to your wanted format. Note it will still work without adding type in this case.

> $http({ > url: 'your/webservice', > method: 'POST', > responseType: 'arraybuffer', > data: json, //this is your json data string > headers: { > 'Content-type': 'application/json', > 'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' > } > }).success(function(data){ > var blob = new Blob([data], { > type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' > }); > saveAs(blob, 'File_Name_With_Some_Unique_Id_Time' + '.xlsx'); > }).error(function(){ > //Some error log > });

Tip! Don't mix " and ', stick to always use ', in a professional environment you will have to pass js validation for example jshint, same goes for using === and not ==, and so on, but that is another topic :)

I would put the save excel in another service, so you have clean structure and the post is in a proper service of its own. I can make a JS fiddle for you, if you don't get my example working. Then I would also need some json data from you that you use for a full example.

Happy coding.. Eduardo

Solution 3 - Json

Download the server response as an array buffer. Store it as a Blob using the content type from the server (which should be application/vnd.openxmlformats-officedocument.spreadsheetml.sheet):

var httpPromise = this.$http.post(server, postData, { responseType: 'arraybuffer' });
httpPromise.then(response => this.save(new Blob([response.data],
	{ type: response.headers('Content-Type') }), fileName));

Save the blob to the user's device:

save(blob, fileName) {
	if (window.navigator.msSaveOrOpenBlob) { // For IE:
		navigator.msSaveBlob(blob, fileName);
	} else { // For other browsers:
		var link = document.createElement('a');
		link.href = window.URL.createObjectURL(blob);
		link.download = fileName;
		link.click();
		window.URL.revokeObjectURL(link.href);
	}
}

Solution 4 - Json

Worked for me -

$scope.downloadFile = function () {
        Resource.downloadFile().then(function (response) {
            var blob = new Blob([response.data], { type: "application/pdf" });
            var objectUrl = URL.createObjectURL(blob);
            window.open(objectUrl);
        },
        function (error) {
            debugger;
        });
    };

Which calls the following from my resource factory-

  downloadFile: function () {
           var downloadRequst = {
                method: 'GET',
                url: 'http://localhost/api/downloadFile?fileId=dfckn4niudsifdh.pdf',
                headers: {
                    'Content-Type': "application/pdf",
                    'Accept': "application/pdf"
                },
                responseType: 'arraybuffer'
            }

            return $http(downloadRequst);
        }

Make sure your API sets the header content type too -

        response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");

Solution 5 - Json

There is no way (to my knowledge) to trigger the download window in your browser from Javascript. The only way to do it is to redirect the browser to a url that streams the file to the browser.

If you can modify your REST service, you might be able to solve it by changing so the POST request doesn't respond with the binary file, but with a url to that file. That'll get you the url in Javascript instead of the binary data, and you can redirect the browser to that url, which should prompt the download without leaving the original page.

Solution 6 - Json

Answer No 5 worked for me ,Suggestion to developer who are facing similar issue.

//////////////////////////////////////////////////////////
//Server side 
//////////////////////////////////////////////////////////
imports ***
public class AgentExcelBuilder extends AbstractExcelView {

protected void buildExcelDocument(Map<String, Object> model,
			HSSFWorkbook workbook, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

        //poi code goes here ....

		response.setHeader("Cache-Control","must-revalidate");
		response.setHeader("Pragma", "public");
		response.setHeader("Content-Transfer-Encoding","binary");
		response.setHeader("Content-disposition", "attachment; filename=test.xls");

		OutputStream output = response.getOutputStream();
		
		workbook.write(output);
		System.out.println(workbook.getActiveSheetIndex());
		System.out.println(workbook.getNumberOfSheets());
		System.out.println(workbook.getNumberOfNames());
		output.flush();
		output.close(); 
}//method buildExcelDocument ENDS

//service.js at angular JS code
function getAgentInfoExcel(workgroup,callback){
		$http({
		    url: CONTEXT_PATH+'/rest/getADInfoExcel',
		    method: "POST",
		    data: workgroup, //this is your json data string
		    headers: {
		       'Content-type': 'application/json'
		    },
		    responseType: 'arraybuffer'
		}).success(function (data, status, headers, config) {
		    var blob = new Blob([data], {type: "application/vnd.ms-excel"});
		    var objectUrl = URL.createObjectURL(blob);
		    window.open(objectUrl);
		}).error(function (data, status, headers, config) {
		    console.log('Failed to download Excel')
		});
	}
////////////////////////////////in .html 

<div class="form-group">`enter code here`
								<a href="javascript:void(0)" class="fa fa-file-excel-o"
									ng-click="exportToExcel();"> Agent Export</a>
							</div>

Solution 7 - Json

You could as well take an alternative approach -- you don't have to use $http, you don't need any extra libraries, and it ought to work in any browser.

Just place an invisible form on your page.

<form name="downloadForm" action="/MyApp/MyFiles/Download" method="post" target="_self">
    <input type="hidden" name="value1" value="{{ctrl.value1}}" />
    <input type="hidden" name="value2" value="{{ctrl.value2}}" />
</form>

And place this code in your angular controller.

ctrl.value1 = 'some value 1';  
ctrl.value2 = 'some value 2';  
$timeout(function () {
    $window.document.forms['downloadForm'].submit();
});

This code will post your data to /MyApp/MyFiles/Download and you'll receive a file in your Downloads folder.
It works with Internet Explorer 10.

If a conventional HTML form doesn't let you post your complex object, then you have two options:

1. Stringify your object and put it into one of the form fields as a string.

<input type="hidden" name="myObjJson" value="{{ctrl.myObj | json:0}}" />


2. Consider HTML JSON forms: https://www.w3.org/TR/html-json-forms/

Solution 8 - Json

I created a service that will do this for you.

Pass in a standard $http object, and add some extra parameters.

  1. A "type" parameter. Specifying the type of file you're retrieving. Defaults to: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  2. A "fileName" parameter. This is required, and should include the extension.

Example:

httpDownloader({
  method : 'POST',
  url : '--- enter the url that returns a file here ---',
  data : ifYouHaveDataEnterItHere,
  type : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // this is the default
  fileName : 'YourFileName.xlsx'
}).then(res => {}).catch(e => {});

That's all you need. The file will be downloaded to the user's device without a popup.

Here's the git repo: https://github.com/stephengardner/ngHttpDownloader

Solution 9 - Json

I was facing this same problem. Let me tell you how I solved it and achieved everything you all seem to be wanting.

Requirements:

  1. Must have a button (or link) to a file - (or a generated memory stream)
  2. Must click the button and have the file download

In my service, (I'm using Asp.net Web API), I have a controller returning an "HttpResponseMessage". I add a "StreamContent" to the response.Content field, set the headers to "application/octet-stream" and add the data as an attachment. I even give it a name "myAwesomeFile.xlsx"

response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StreamContent(memStream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "myAwesomeFile.xlsx" };

Now here's the trick ;)

I am storing the base URL in a text file that I read into a variable in an Angular Value called "apiRoot". I do this by declaring it and then setting it on the "run" function of the Module, like so:

app.value('apiRoot', { url: '' });
app.run(function ($http, apiRoot) {
    $http.get('/api.txt').success(function (data) {
        apiRoot.url = data;
    });
});

That way I can set the URL in a text file on the server and not worry about "blowing it away" in an upload. (You can always change it later for security reasons - but this takes the frustration out of development ;) )

And NOW the magic:

All I'm doing is creating a link with a URL that directly hits my service endpoint and target's a "_blank".

<a ng-href="{{vm.getFileHref(FileId)}}" target="_blank" class="btn btn-default">&nbsp;Excel File</a>

the secret sauce is the function that sets the href. You ready for this?

vm.getFileHref = function (Id) {
    return apiRoot.url + "/datafiles/excel/" + Id;
}

Yep, that's it. ;)

Even in a situation where you are iterating over many records that have files to download, you simply feed the Id to the function and the function generates the url to the service endpoint that delivers the file.

Hope this helps!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlex ManView Question on Stackoverflow
Solution 1 - JsonJorgView Answer on Stackoverflow
Solution 2 - JsonEduardo in NorwayView Answer on Stackoverflow
Solution 3 - JsonEdward BreyView Answer on Stackoverflow
Solution 4 - JsonJonathan ForsterView Answer on Stackoverflow
Solution 5 - JsonAnders EkdahlView Answer on Stackoverflow
Solution 6 - JsonVj SinghView Answer on Stackoverflow
Solution 7 - JsonEvgeny NikitinView Answer on Stackoverflow
Solution 8 - JsonAugie GardnerView Answer on Stackoverflow
Solution 9 - Jsonuser1628627View Answer on Stackoverflow