Просте завантаження файлів Angularjs змушує маршрутизатор перенаправляти


78

HTML:

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

Завантаження отримує унікальне ім'я файлу, тоді як справжнє ім'я зберігається в базі даних. Я хочу здійснити просте завантаження файлу. Але наведений вище код переспрямовує на / через:

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

Я пробував з

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

але це просто відкриває файл у новому вікні.

Будь-які ідеї, як увімкнути реальне завантаження для будь-якого типу файлу?


11
ти пробував target="_blank"чи target="_self"? Див .: docs.angularjs.org/guide/…
Моріц Петерсен

2
@MoritzPetersen target = "_ self" чудово працює, зробіть це, будь ласка,
Проголосуйте

6
Прийміть відповідь на jessegavins, оскільки я не міг би написати її краще.
Моріц Петерсен

Моріц, посилання зараз порушено - повинно бути docs.angularjs.org/guide/$location#html-link-rewriting
Рікі Кларксон,

Відповіді:


114

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

У подібних випадках посилання не переписуються; натомість браузер виконає повне перезавантаження сторінки до вихідного посилання.

  • Посилання, які містять цільовий елемент Приклад:
    <a href="https://stackoverflow.com/ext/link?a=b" target="_self">link</a>

  • Абсолютні посилання, які переходять до іншого домену Приклад:
    <a href="http://angularjs.org/">link</a>

  • Посилання, що починаються на '/', що ведуть до іншого базового шляху, коли база визначена Приклад:
    <a href="https://stackoverflow.com/not-my-base/link">link</a>

Отже, у вашому випадку ви повинні додати цільовий атрибут приблизно так ...

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

Абсолютна URL-адреса не працюватиме, якщо посилання вказує на той самий сайт.
Jan Święcki

1
Це чудова відповідь. Тепер мені просто потрібно зрозуміти, як це зробити за допомогою кнопки та повідомлення POST 8- /
Snekse

1
@Snekse Якщо вам потрібно завантажити файл за допомогою кнопки та POST, просто створіть звичайний тег <form> та <button type = "submit">, як це було у 1996 році
jessegavin

1
:-) Я боявся, що ти збираєшся це сказати. Я намагався уникнути форми, оскільки всі дані, які я публікую, були створені, а не введені користувачем.
Snekse

1
Одне зауваження, downloadне підтримується IE або Safari.
Ashish Gaur

32

Нам також довелося розробити рішення, яке б навіть працювало з API, що вимагають автентифікації (див. Цю статтю )

Використання AngularJS в двох словах ось як ми це зробили:

Крок 1: Створіть спеціальну директиву

// 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);
            });
        };
    }] 
});

Крок 2: Створіть шаблон

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

Крок 3: Використовуйте його

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

Це відобразить синю кнопку. Після натискання PDF-файл буде завантажений (Увага: серверний сервер повинен доставити PDF-файл у кодуванні Base64!) І помістити його у href. Кнопка стає зеленою і перемикає текст на Зберегти . Користувач може натиснути ще раз і перед ним з’явиться стандартне діалогове вікно завантаження файлу для файлу my-awesome.pdf .

У нашому прикладі використовуються файли PDF, але, мабуть, ви можете надати будь-який двійковий формат, якщо він правильно закодований.


2
Приємне рішення, але є два обмеження: 1. Користувач повинен натиснути кнопку двічі, 2. IE 11 не підтримує атрибут завантаження, тому ви не можете встановити ім'я файлу.
Louis Haußknecht

4
А як щодо великих файлів? 1 Гб? 10 Гб?
ecdeveloper

8

Якщо вам потрібна більш вдосконалена директива, я рекомендую рішення, яке я впровадив, правильно протестоване в Internet Explorer 11, Chrome та FireFox.

Я сподіваюся, це буде корисним.

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>

ДИРЕКТИВА:

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');
                    }
                }
            });

        }
    }
})

У КОНТРОЛЕРІ:

$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=[];
                                });
                            }; 

В СЛУЖБІ:

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;
                };

БЕКЕНД (на ВЕСНУ):

@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
Чи правильно я розумію: весь завантажуваний файл читається у просторі даних Javascript, а потім передається браузеру для запису в локальний файл? Уявіть, що дані мають 1 Гб або більше; Я думаю, що використання простого тегу <a> вище буде передаватися браузером поступово. Я не впевнений, що об’єднання всіх даних в єдиний масив є практичним у моєму випадку.
Марк Лафф,

Так, це правильно, міг бути використаний простий тег <a>, але в моєму випадку я реалізував це з двох причин, перша причина полягає в тому, що в моєму випадку мені потрібно побудувати excel динамічно з даними з бази даних; а друга причина полягає в тому, що простий тег <a> не працює в IE.
Хавеліно

0

у шаблоні

<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>

в контролері

     $scope.export = function(){ $window.location.href = $scope.export; };
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.