Angularjs einfacher Dateidownload bewirkt, dass der Router umleitet


78

HTML:

<a href="mysite.com/uploads/asd4a4d5a.pdf" download="foo.pdf">

Uploads erhalten einen eindeutigen Dateinamen, während der tatsächliche Name in der Datenbank gespeichert wird. Ich möchte einen einfachen Dateidownload realisieren. Der obige Code leitet jedoch zu / wegen:

$routeProvider.otherwise({
    redirectTo: '/', 
    controller: MainController
});

Ich habe es mit versucht

$scope.download = function(resource){
    window.open(resource);
}

Dies öffnet jedoch nur die Datei in einem neuen Fenster.

Irgendwelche Ideen, wie man einen echten Download für jeden Dateityp ermöglicht?


11
hast du es versucht target="_blank"oder target="_self"? Siehe: docs.angularjs.org/guide/…
Moritz Petersen

2
@ MoritzPetersen target = "_ self" funktioniert großartig, machen Sie dies bitte eine Antwort
Upvote

6
Bitte akzeptieren Sie Jessegavins Antwort, da ich es nicht besser hätte schreiben können.
Moritz Petersen

Moritz, die Verbindung ist jetzt unterbrochen - sollte docs.angularjs.org/guide/$location#html-link-rewriting sein
Ricky Clarkson

Antworten:


114

https://docs.angularjs.org/guide/$location#html-link-rewriting

In Fällen wie den folgenden werden Links nicht neu geschrieben. Stattdessen führt der Browser ein erneutes Laden der gesamten Seite zum ursprünglichen Link durch.

  • Links, die Zielelemente enthalten Beispiel:
    <a href="https://stackoverflow.com/ext/link?a=b" target="_self">link</a>

  • Absolute Links, die zu einer anderen Domain führen Beispiel:
    <a href="http://angularjs.org/">link</a>

  • Links, die mit '/' beginnen und zu einem anderen Basispfad führen, wenn base definiert ist Beispiel:
    <a href="https://stackoverflow.com/not-my-base/link">link</a>

In Ihrem Fall sollten Sie also ein Zielattribut wie folgt hinzufügen ...

<a target="_self" href="example.com/uploads/asd4a4d5a.pdf" download="foo.pdf">

Die absolute URL funktioniert nicht, wenn der Link auf dieselbe Site verweist.
Jan Święcki

1
Dies ist eine großartige Antwort. Jetzt muss ich nur noch herausfinden, wie das mit einem Knopf und einem POST 8- /
Snekse

1
@Snekse Wenn Sie einen Dateidownload mit einer Schaltfläche und einem POST durchführen müssen, erstellen Sie einfach ein reguläres <form> -Tag und ein <button type = "
submit

1
:-) Ich hatte Angst, dass du das sagen würdest. Ich habe versucht, ein Formular zu vermeiden, da alle von mir veröffentlichten Daten generiert wurden und keine Benutzereingaben.
Snekse

1
Ein zu beachtender Punkt downloadwird von IE oder Safari nicht unterstützt.
Ashish Gaur

32

Wir mussten auch eine Lösung entwickeln, die sogar mit APIs funktioniert, die eine Authentifizierung erfordern (siehe diesen Artikel ).

AngularJS auf den Punkt gebracht: So haben wir es gemacht:

Schritt 1: Erstellen Sie eine dedizierte Direktive

// jQuery needed, uses Bootstrap classes, adjust the path of templateUrl
app.directive('pdfDownload', function() {
return {
    restrict: 'E',
    templateUrl: '/path/to/pdfDownload.tpl.html',
    scope: true,
    link: function(scope, element, attr) {
        var anchor = element.children()[0];

        // When the download starts, disable the link
        scope.$on('download-start', function() {
            $(anchor).attr('disabled', 'disabled');
        });

        // When the download finishes, attach the data to the link. Enable the link and change its appearance.
        scope.$on('downloaded', function(event, data) {
            $(anchor).attr({
                href: 'data:application/pdf;base64,' + data,
                download: attr.filename
            })
                .removeAttr('disabled')
                .text('Save')
                .removeClass('btn-primary')
                .addClass('btn-success');

            // Also overwrite the download pdf function to do nothing.
            scope.downloadPdf = function() {
            };
        });
    },
    controller: ['$scope', '$attrs', '$http', function($scope, $attrs, $http) {
        $scope.downloadPdf = function() {
            $scope.$emit('download-start');
            $http.get($attrs.url).then(function(response) {
                $scope.$emit('downloaded', response.data);
            });
        };
    }] 
});

Schritt 2: Erstellen Sie eine Vorlage

<a href="" class="btn btn-primary" ng-click="downloadPdf()">Download</a>

Schritt 3: Verwenden Sie es

<pdf-download url="/some/path/to/a.pdf" filename="my-awesome-pdf"></pdf-download>

Dadurch wird eine blaue Schaltfläche gerendert. Wenn Sie darauf klicken, wird ein PDF heruntergeladen (Achtung: Das Backend muss das PDF in Base64-Codierung liefern!) Und in die href eingefügt. Die Schaltfläche wird grün und schaltet den Text auf Speichern . Der Benutzer kann erneut klicken und erhält einen Standarddialog für die Download-Datei für die Datei my-awesome.pdf .

In unserem Beispiel werden PDF-Dateien verwendet, aber anscheinend können Sie jedes Binärformat bereitstellen, wenn es ordnungsgemäß codiert ist.


2
Gute Lösung, aber es gibt zwei Einschränkungen: 1. Der Benutzer muss zweimal auf die Schaltfläche klicken. 2. IE 11 unterstützt das Download-Attribut nicht, sodass Sie keinen Dateinamen festlegen können.
Louis Haußknecht

4
Was ist mit großen Dateien? 1 GB? 10 GB?
Ecdeveloper

8

Wenn Sie eine erweiterte Richtlinie benötigen, empfehle ich die von mir implementierte Lösung, die in Internet Explorer 11, Chrome und FireFox korrekt getestet wurde.

Ich hoffe es wird hilfreich sein.

HTML:

<a href="#" class="btn btn-default" file-name="'fileName.extension'"  ng-click="getFile()" file-download="myBlobObject"><i class="fa fa-file-excel-o"></i></a>

RICHTLINIE:

directive('fileDownload',function(){
    return{
        restrict:'A',
        scope:{
            fileDownload:'=',
            fileName:'=',
        },

        link:function(scope,elem,atrs){


            scope.$watch('fileDownload',function(newValue, oldValue){

                if(newValue!=undefined && newValue!=null){
                    console.debug('Downloading a new file'); 
                    var isFirefox = typeof InstallTrigger !== 'undefined';
                    var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
                    var isIE = /*@cc_on!@*/false || !!document.documentMode;
                    var isEdge = !isIE && !!window.StyleMedia;
                    var isChrome = !!window.chrome && !!window.chrome.webstore;
                    var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
                    var isBlink = (isChrome || isOpera) && !!window.CSS;

                    if(isFirefox || isIE || isChrome){
                        if(isChrome){
                            console.log('Manage Google Chrome download');
                            var url = window.URL || window.webkitURL;
                            var fileURL = url.createObjectURL(scope.fileDownload);
                            var downloadLink = angular.element('<a></a>');//create a new  <a> tag element
                            downloadLink.attr('href',fileURL);
                            downloadLink.attr('download',scope.fileName);
                            downloadLink.attr('target','_self');
                            downloadLink[0].click();//call click function
                            url.revokeObjectURL(fileURL);//revoke the object from URL
                        }
                        if(isIE){
                            console.log('Manage IE download>10');
                            window.navigator.msSaveOrOpenBlob(scope.fileDownload,scope.fileName); 
                        }
                        if(isFirefox){
                            console.log('Manage Mozilla Firefox download');
                            var url = window.URL || window.webkitURL;
                            var fileURL = url.createObjectURL(scope.fileDownload);
                            var a=elem[0];//recover the <a> tag from directive
                            a.href=fileURL;
                            a.download=scope.fileName;
                            a.target='_self';
                            a.click();//we call click function
                        }


                    }else{
                        alert('SORRY YOUR BROWSER IS NOT COMPATIBLE');
                    }
                }
            });

        }
    }
})

IM CONTROLLER:

$scope.myBlobObject=undefined;
$scope.getFile=function(){
        console.log('download started, you can show a wating animation');
        serviceAsPromise.getStream({param1:'data1',param1:'data2', ...})
        .then(function(data){//is important that the data was returned as Aray Buffer
                console.log('Stream download complete, stop animation!');
                $scope.myBlobObject=new Blob([data],{ type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
        },function(fail){
                console.log('Download Error, stop animation and show error message');
                                    $scope.myBlobObject=[];
                                });
                            }; 

IM DIENST:

function getStream(params){
                 console.log("RUNNING");
                 var deferred = $q.defer();

                 $http({
                     url:'../downloadURL/',
                     method:"PUT",//you can use also GET or POST
                     data:params,
                     headers:{'Content-type': 'application/json'},
                     responseType : 'arraybuffer',//THIS IS IMPORTANT
                    })
                    .success(function (data) {
                        console.debug("SUCCESS");
                        deferred.resolve(data);
                    }).error(function (data) {
                         console.error("ERROR");
                         deferred.reject(data);
                    });

                 return deferred.promise;
                };

BACKEND (am FRÜHLING):

@RequestMapping(value = "/downloadURL/", method = RequestMethod.PUT)
public void downloadExcel(HttpServletResponse response,
        @RequestBody Map<String,String> spParams
        ) throws IOException {
        OutputStream outStream=null;
outStream = response.getOutputStream();//is important manage the exceptions here
ObjectThatWritesOnOutputStream myWriter= new ObjectThatWritesOnOutputStream();// note that this object doesn exist on JAVA,
ObjectThatWritesOnOutputStream.write(outStream);//you can configure more things here
outStream.flush();
return;
}

1
Verstehe ich das richtig: Die gesamte herunterzuladende Datei wird in den Javascript-Datenbereich eingelesen und dann an den Browser übergeben, um in die lokale Datei zu schreiben? Stellen Sie sich vor, die Daten sind 1 GB oder mehr. Ich denke, dass die Verwendung des einfachen <a> -Tags oben vom Browser schrittweise gestreamt wird. Ich bin mir nicht sicher, ob es in meinem Fall praktisch ist, alle Daten in einem einzigen Arraystring zusammenzufassen.
Mark Laff

Ja, das ist richtig, ein einfaches <a> -Tag könnte verwendet werden, aber in meinem Fall habe ich dies aus zwei Gründen implementiert. Der erste Grund ist, dass ich in meinem Fall ein Excel dinamisch mit Daten aus der Datenbank erstellen muss. und der zweite Grund ist, dass ein einfaches <a> -Tag im IE nicht funktioniert.
Havelino

0

in Vorlage

<md-button class="md-fab md-mini md-warn md-ink-ripple" ng-click="export()" aria-label="Export">
<md-icon class="material-icons" alt="Export" title="Export" aria-label="Export">
    system_update_alt
</md-icon></md-button>

in der Steuerung

     $scope.export = function(){ $window.location.href = $scope.export; };
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.