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/…
Moritz Petersen

2
@MoritzPetersen target = "_ self"가 훌륭하게 작동합니다.이 질문에 답해주세요
Upvote

6
내가 더 잘 쓸 수 없었기 때문에 jessegavins의 대답을 받아들이십시오.
Moritz Petersen 2013 년

Moritz, 이제 링크가 끊어졌습니다 -docs.angularjs.org/guide/$location#html-link-rewriting
Ricky Clarkson

답변:


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이 작동하지 않습니다.
월 Święcki

1
이것은 훌륭한 대답입니다. 이제 버튼과 POST 8- /
Snekse

1
@Snekse 버튼과 POST로 파일을 다운로드해야한다면 1996 년에했던 것처럼 일반 <form> 태그와 <button type = "submit">을
만드십시오

1
:-) 나는 당신이 그렇게 말 할까 두려웠습니다. 내가 게시하는 모든 데이터가 사용자 입력이 아니라 생성되었으므로 양식을 피하려고했습니다.
Snekse 2014 년

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은 다운로드 속성을 지원하지 않으므로 파일 이름을 설정할 수 없습니다.
루이 Haußknecht

4
큰 파일은 어떻습니까? 1GB? 10GB?
ecdeveloper 2016 년

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 데이터 공간으로 읽은 다음 브라우저로 전달하여 로컬 파일에 기록합니까? 데이터가 1GB 이상이라고 상상해보십시오. 위의 간단한 <a> 태그를 사용하면 브라우저에서 점진적으로 스트리밍 될 것이라고 생각합니다. 모든 데이터를 단일 배열 문자열로 가져 오는 것이 제 경우에는 실용적 일지 모르겠습니다.
Mark Laff

예, 맞습니다. 간단한 <a> 태그를 사용할 수 있지만 제 경우에는 두 가지 이유로이를 구현했습니다. 첫 번째 이유는 제 경우에는 데이터베이스의 데이터로 엑셀을 동적으로 빌드해야한다는 것입니다. 두 번째 이유는 간단한 <a> 태그가 IE에서 작동하지 않는다는 것입니다.
havelino

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; };
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.