AngularJs dynamic download from response

angularjs, internet-explorer-11, javascript

Solution

Problem:

I have resolved this issue. It appears that Internet Explorer 11 does not like aliases to the `msSaveBlob` function, demonstrated by the simplest examples:

// Succeeds
navigator.msSaveBlob(new Blob(["Hello World"]), "test.txt");

// Fails with "Invalid calling object"
var saveBlob = navigator.msSaveBlob;
saveBlob(new Blob(["Hello World"]), "test.txt");

So essentially the generic alias created to encapsulate `saveBlob` functionality, which should be permissible, prevents `msSaveBlob` from working as expected:

var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;

Workaround:

So the work around is to test for `msSaveBlob` separately.

if(navigator.msSaveBlob)
    navigator.msSaveBlob(blob, filename);
else {
    // Try using other saveBlob implementations, if available
    var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
    if(saveBlob === undefined) throw "Not supported";
    saveBlob(blob, filename);
}

tl;dr

So the `msSaveBlob` method works, as long as you don't alias the function. Perhaps it's a security precaution - but then perhaps a security exception would have been more appropriate, though I think it is most likely a flaw, considering the source. :)

Problem

I have written a directive based on Scott's answer. You'd use it like so: ``` <button class="btn btn-success" download-response="getData()" download-success="getDataSuccess()" download-error="getDataError()" download-name="{{name}}.pdf" download-backup-url="/Backup/File.pdf"> Save </button> ``` Issue: the code below throws an error `TypeError: Invalid calling object` in `IE11` on the first method (line: `saveBlob(blob, filename);`). Even though it falls back to other methods of downloading, it is my understanding that the saveMethod1 should work in `IE11`. Here is the code: ``` 'use strict'; // directive allows to provide a function to be executed to get data to be downloaded // attributes: // download-response - Required. Function to get data. It must return a promise. It must be declared on the $scope. // download-success - Optional. Function to be executed if download-response function was successfully resolved. It must be declared on the $scope. // download-error - Optional. Function to be executed if download-response function return a promise rejection. It must be declared on the $scope. // download-mime - Optional. provide a mime type of data being downloaded. Defaulted to "application/octet-stream" // download-name - Optional. name of the file to download. Defaulted to "download.bin" // download-backup-url - in case browser does not support dynamic download, this url will be called to get the file angular.module('app.directives') .directive('downloadResponse', [ '$parse', '$timeout', function ($parse, $timeout) { function saveMethod1(data, filename, contentType) { // Support for saveBlob method (Currently only implemented in Internet Explorer as msSaveBlob, other extension in case of future adoption) var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob; if (saveBlob) { // Save blob is supported, so get the blob as it's contentType and call save. var blob = new Blob([data], { type: contentType }); saveBlob(blob, filename); //console.log("SaveBlob Success"); } else { throw 'saveBlob is not supported. Falling back to the next method'; } } function saveMethod2(data, filename, contentType, octetStreamMime) { // Get the blob url creator var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL; if (urlCreator) { // Try to use a download link var link = document.createElement("a"); var url; if ("download" in link) { // Prepare a blob URL var blob = new Blob([data], { type: contentType }); url = urlCreator.createObjectURL(blob); link.setAttribute("href", url); // Set the download attribute (Supported in Chrome 14+ / Firefox 20+) link.setAttribute("download", filename); // Simulate clicking the download link var event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); link.dispatchEvent(event); //console.log("Download link Success"); } else { // Prepare a blob URL // Use application/octet-stream when using window.location to force download var blob = new Blob([data], { type: octetStreamMime }); url = urlCreator.createObjectURL(blob); window.location = url; //console.log("window.location Success"); } } else { throw 'UrlCreator not supported. Falling back to the next method'; } } function saveMethod3(attrs) { if (attrs.downloadBackupUrl && attrs.downloadBackupUrl != '') { console.log('opening ' + attrs.downloadBackupUrl); window.open('http://' + document.domain + attrs.downloadBackupUrl, '_blank'); } else { throw 'Could not download a file using any of the available methods. Also you did not provide a backup download link. No more bullets left...'; } } return { restrict: 'A', scope: false, link:function (scope, elm, attrs) { var getDataHandler = $parse(attrs.downloadResponse); elm.on('click', function() { var promise = getDataHandler(scope); promise.then( function (data) { if (attrs.downloadSuccess && attrs.downloadSuccess != '') { var successHandler = $parse(attrs.downloadSuccess); successHandler(scope); } var octetStreamMime = "application/octet-stream"; var filename = attrs.downloadName || "download.bin"; var contentType = attrs.downloadMime || octetStreamMime; try { saveMethod1(data, filename, contentType); return; } catch (e) { console.log(e); try { saveMethod2(data, filename, contentType, octetStreamMime); return; } catch (e) { console.log(e); try { saveMethod3(attrs); return; } catch (e) { throw e; } throw e; } throw e; } }, function(data) { if (attrs.downloadError && attrs.downloadError != '') { var errorHandler = $parse(attrs.downloadError); errorHandler(scope); } } ); }); } }; } ]); ``` Any help is greatly appreciated!

Original source

Related problems