3

File is not downloading at browser. I'm preparing the file and writing it to output stream of response.

Rest API is there:

@RequestMapping(value = "/export-companies",
        method = {RequestMethod.GET, RequestMethod.HEAD})
    @Timed
    public void downloadCompanies(HttpServletResponse response) throws URISyntaxException {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet("Sample sheet");

        Map<String, Object[]> data = new HashMap<String, Object[]>();
        data.put("1", new Object[] {"Emp No.", "Name", "Salary"});
        data.put("2", new Object[] {1d, "John", 1500000d});
        data.put("3", new Object[] {2d, "Sam", 800000d});
        data.put("4", new Object[] {3d, "Dean", 700000d});

        Set<String> keyset = data.keySet();
        int rownum = 0;
        for (String key : keyset) {
            Row row = sheet.createRow(rownum++);
            Object [] objArr = data.get(key);
            int cellnum = 0;
            for (Object obj : objArr) {
                Cell cell = row.createCell(cellnum++);
                if(obj instanceof Date)
                    cell.setCellValue((Date)obj);
                else if(obj instanceof Boolean)
                    cell.setCellValue((Boolean)obj);
                else if(obj instanceof String)
                    cell.setCellValue((String)obj);
                else if(obj instanceof Double)
                    cell.setCellValue((Double)obj);
            }
        }

        try {
            ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
            workbook.write(outByteStream);
            byte [] outArray = outByteStream.toByteArray();
            response.setContentType("application/ms-excel");
            response.setContentLength(outArray.length);
            response.setHeader("Expires:", "0"); // eliminates browser caching
            response.setHeader("Content-Disposition", "attachment; filename=template.xls");
            OutputStream outStream = response.getOutputStream();
            outStream.write(outArray);
            outStream.flush();
            workbook.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

From front end (using Angular JS):

(function() {
    'use strict';

    angular
        .module('MyApp')
        .factory('CompanyExportService', CompanyExportService);

    CompanyExportService.$inject = ['$resource'];

    function CompanyExportService ($resource) {
        var service = $resource('api/export-companies', {}, {
            'get': {
                method: 'GET',
                isArray: false
            }
        });

        return service;
    }
})();

File contents are there in response as non-readable format. But file is not downloaded at browser.

centic
  • 15,565
  • 9
  • 68
  • 125
fatCop
  • 2,556
  • 10
  • 35
  • 57

3 Answers3

5

Angular will receive the file contents mere character sequences. You need to create a file from these characters and initiate the browser download in frontend.

You can do it like this -

var blob = new Blob([data], 
                    {type: 'application/vnd.openxmlformat-officedocument.spreadsheetml.sheet;'});
saveAs(blob, fileName);

where data is the response you received form your API. The saveAs function is part of FileSaver.js library. Although you can look on how to manually do that but why reinvent the wheel?

Harshil Sharma
  • 2,016
  • 1
  • 29
  • 54
  • 2
    I use this same code to save excel file that returned from response.build() method of Response object of rest api. Although it save file, but while I try to open the saved excel file, it show file corrupt error message. – Salahin Rocky Nov 03 '16 at 09:40
  • Did you get a solution for this? am facing the same issue. – Chiya Oct 17 '17 at 16:28
  • This works for me. Just one question though. How do I retain the filename sent by server? That is I don't wanna supply my own `fileName` in `saveAs(blob, fileName)`. Thank you. – Julez Feb 08 '19 at 01:44
3

Downloading files with XHR is problematic. As long as you do only GET requests, there exists much simpler approach to trigger browser to download file.

Use JavaScript native method window.open(url). It does work well in all browsers including IE9.

In code below, I use $window, which is Angular's proxy for native window object.

Example for your code could be like:

(function() {
'use strict';

angular
    .module('MyApp')
    .factory('CompanyExportService', CompanyExportService);

CompanyExportService.$inject = ['$window'];

function CompanyExportService ($window) {
    var exportUrl = 'api/export-companies';

    return {
        download: download
    }

    function download() {
        $window.open(exportUrl);
    }
}
})();

Note that this action is out of scope of Angular, you can't do much about error handling or waiting till the file will be downloaded. Might be problem if you want to generate huge Excel files or your API is slow.

For more details, read question: Spring - download response as a file

Update:

I've replaced window.location.href with window.open() which seems to be better choice for downloading files.

If your API will throw an error page instead of file, window.location.href will replace current page (thus losing its state). $window.open() however will opens this error in new tab without losing current state of of application.

Community
  • 1
  • 1
Piotr Lewandowski
  • 6,540
  • 2
  • 27
  • 32
  • hi @Piotr Lewandowski kindly check my fiddle once https://jsfiddle.net/x30v0bym/3/ file download is wokring correctly but excel not open properly in microsoft excel i am not getting cells it's show data with white paper help – jose Jun 16 '17 at 14:10
  • @jose your problem is that you don't generate content in appropriate format. You've only saved HTML into file and named it with *xls* extension. It won't auto-magically be converted into excel file format. *Ask another question* as it's much more space needed to describe proper way of doing it. – Piotr Lewandowski Jun 17 '17 at 08:58
  • @jose I found you already asked question. I've answered it here: https://stackoverflow.com/a/44602960/2757140 – Piotr Lewandowski Jun 17 '17 at 09:47
3

You can download file in new tab. Modern browser are closing them automatically when downloading is completed.

By opening new window you get reference to it, when downloading is completed then window.closed is set to true.

Unfortunatelly you need to check from time-to-time this param inside interval ...

var newWindowRef = $window.open(url, name);
if (newWindowRef) {
    if (newWindowRef.document.body) { // not working on IE
        newWindowRef.document.title = "Downloading ...";
        newWindowRef.document.body.innerHTML = '<h4>Your file is generating ... please wait</h4>';
    }

    var interval = setInterval(function() {
        if (!!newWindowRef.closed) {
            // Downloading completed
            clearInterval(interval);
        }
    }, 1000);
} else {
    $log.error("Opening new window is probably blocked");
}

Tested and works on Chrome v52, FF v48 and IE 11

BartB
  • 116
  • 3