Como faço para baixar um arquivo com Angular2 ou superior

181

Eu tenho um aplicativo WebApi / MVC para o qual estou desenvolvendo um cliente angular2 (para substituir o MVC). Estou tendo alguns problemas para entender como o Angular salva um arquivo.

A solicitação está ok (funciona bem com o MVC, e podemos registrar os dados recebidos), mas não consigo descobrir como salvar os dados baixados (estou seguindo a mesma lógica deste post ). Tenho certeza de que é estupidamente simples, mas até agora simplesmente não estou entendendo.

O código da função do componente está abaixo. Eu tentei diferentes alternativas, a maneira blob deve ser o caminho a percorrer, tanto quanto eu entendi, mas não há nenhuma função createObjectURLno URL. Não consigo nem encontrar a definição de URLna janela, mas aparentemente ela existe. Se eu usar o FileSaver.jsmódulo , recebo o mesmo erro. Acho que isso mudou recentemente ou ainda não foi implementado. Como posso acionar o salvamento do arquivo em A2?

downloadfile(type: string){

    let thefile = {};
    this.pservice.downloadfile(this.rundata.name, type)
        .subscribe(data => thefile = new Blob([data], { type: "application/octet-stream" }), //console.log(data),
                    error => console.log("Error downloading the file."),
                    () => console.log('Completed file download.'));

    let url = window.URL.createObjectURL(thefile);
    window.open(url);
}

Por uma questão de integridade, o serviço que busca os dados está abaixo, mas a única coisa que faz é emitir a solicitação e transmitir os dados sem mapear, se for bem-sucedido:

downloadfile(runname: string, type: string){
   return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
            .catch(this.logAndPassOn);
}
rll
fonte
Você não pode baixar arquivos grandes com esse método. Você atingirá o limite de memória por guia. Isso pode ser tão baixo quanto 1-2 GB.
Matthew B.
@MatthewB. gostaria de ter dito o que era melhor.
28419 steve
Para downloads de arquivos grandes, é necessário especificar uma nova guia, por exemplo, se simular um clique em <A>, o destino precisa ser igual a "_blank" ou enviar um formulário. Eu não acho que exista uma maneira limpa de contornar a grande limitação de tamanho de arquivo com solicitações no estilo Ajax.
Matthew B.

Respostas:

180

O problema é que o observável é executado em outro contexto; portanto, ao tentar criar a URL var, você tem um objeto vazio e não o blob que deseja.

Uma das muitas maneiras existentes para resolver isso é a seguinte:

this._reportService.getReport().subscribe(data => this.downloadFile(data)),//console.log(data),
                 error => console.log('Error downloading the file.'),
                 () => console.info('OK');

Quando a solicitação estiver pronta, chamará a função "downloadFile", definida da seguinte maneira:

downloadFile(data: Response) {
  const blob = new Blob([data], { type: 'text/csv' });
  const url= window.URL.createObjectURL(blob);
  window.open(url);
}

o blob foi criado perfeitamente e, portanto, o URL var, se não abrir a nova janela, verifique se você já importou 'rxjs / Rx';

  import 'rxjs/Rx' ;

Espero que isso possa ajudá-lo.

Alejandro Corredor
fonte
9
O que é this._reportService.getReport()e o que ele retorna?
Burjua
3
@Burjua os getReport()retornos umthis.http.get(PriceConf.download.url)
ji-ruh
6
O problema que estou tendo é que a janela abre e fecha imediatamente não o download do arquivo
Braden Brown
7
Como podemos definir o nome do arquivo aqui? por padrão, ele escolhe um valor numérico como nome
Saurabh
8
Usei o código acima para baixar um arquivo da resposta da API, mas estou recebendo algum erro ao criar a parte do Blob "A resposta do tipo não é atribuível ao tipo Blobpart". Ajuda gentilmente se alguém conhece esta questão
knbibin
91

Tente isso !

1 - Instale dependências para mostrar salvar / abrir arquivo pop-up

npm install file-saver --save
npm install @types/file-saver --save

2- Crie um serviço com esta função para recuperar os dados

downloadFile(id): Observable<Blob> {
    let options = new RequestOptions({responseType: ResponseContentType.Blob });
    return this.http.get(this._baseUrl + '/' + id, options)
        .map(res => res.blob())
        .catch(this.handleError)
}

3- No componente, analise o blob com 'file-saver'

import {saveAs as importedSaveAs} from "file-saver";

  this.myService.downloadFile(this.id).subscribe(blob => {
            importedSaveAs(blob, this.fileName);
        }
    )

Isso funciona para mim!

Hector Cuevas
fonte
1
Eu usei o passo 2 em combinação com a resposta de @Alejandro e funcionou sem a necessidade de instalar arquivo-saver ...
Ewert
5
Obrigado! Funciona perfeitamente! Gostaria de saber se podemos obter o nome do arquivo definido no cabeçalho da resposta. Isso é possível?
jfajunior
2
erro Av5 O argumento do tipo 'RequestOptions' não é atribuível ao parâmetro do tipo '{headers ?: HttpHeaders | {[header: string]: string | corda[]; };
giveJob
Este, no entanto, não é adequado para o download de arquivos grandes.
Reven 03/04
61

Se você não precisar adicionar cabeçalhos na solicitação, para baixar um arquivo no Angular2, faça um simples :

window.location.href='http://example.com/myuri/report?param=x';

no seu componente.

surfealokesea
fonte
4
Alguém pode dizer por que esta resposta foi rebaixada? O tópico é baixar um arquivo usando angular2. Se esse método funcionar para fazer um download simples, também deverá ser marcado como uma resposta válida.
Saurabh Shetty
5
@SaurabhShetty, isso não ajudará no caso de você desejar enviar cabeçalhos personalizados, e se você quiser enviar um token de autenticação, por exemplo? Se você examinar a questão do OP, poderá ver que ele usa authHttp!
A.Akram
6
Eu entendo os votos negativos, mas essa resposta resolveu meu problema.
precisa saber é o seguinte
1
Se você deixar o servidor retornar a URL em algum contexto, ele poderá prepará-la. ex: objeto: MyRecord.Cover. A capa pode ser um URL para uma imagem no servidor. Ao chamar get (Myrecord), você permite que o servidor retorne a URL preparada (Cover), com o token de segurança e outros cabeçalhos definidos.
Jens Alenius
2
É uma resposta que funciona. Apenas porque ele não tem <inserir recurso útil aqui> que não o torna uma resposta.
gburton
46

Isto é para pessoas que querem saber como fazê-lo usando HttpClient e economizador de arquivos:

  1. Instalar protetor de arquivo

npm install file-saver - salvar

npm install @ types / file-saver - salvar

Classe de serviço da API:

export() {
    return this.http.get(this.download_endpoint, 
        {responseType: 'blob'});
}

Componente:

import { saveAs } from 'file-saver';
exportPdf() {
    this.api_service.export().subscribe(data => saveAs(data, `pdf report.pdf`));
}
Justin
fonte
1
Como mostrar o tamanho do arquivo no navegador quando o download é iniciado? Estou enviando o tamanho do arquivo como tamanho do conteúdo no cabeçalho http.
precisa saber é o seguinte
35

Que tal agora?

this.http.get(targetUrl,{responseType:ResponseContentType.Blob})
        .catch((err)=>{return [do yourself]})
        .subscribe((res:Response)=>{
          var a = document.createElement("a");
          a.href = URL.createObjectURL(res.blob());
          a.download = fileName;
          // start download
          a.click();
        })

Eu poderia fazer com isso.
Não é necessário pacote adicional.

harufumi.abe
fonte
3
Tão simples, porém, é aquele que funciona perfeitamente. Não bagunça o DOM, não cria nenhum elemento. Combinei esta solução com algumas das opções acima e funciona como um encanto.
Chax
20

Como mencionado por Alejandro Corredor , é um erro de escopo simples. O subscribeé executado de forma assíncrona e opendeve ser colocado nesse contexto, para que os dados terminem de carregar quando acionarmos o download.

Dito isto, existem duas maneiras de fazê-lo. Como os documentos recomendam, o serviço cuida de obter e mapear os dados:

//On the service:
downloadfile(runname: string, type: string){
  var headers = new Headers();
  headers.append('responseType', 'arraybuffer');
  return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
            .map(res => new Blob([res],{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }))
            .catch(this.logAndPassOn);
}

Então, no componente, apenas assinamos e lidamos com os dados mapeados. Existem duas possibilidades. O primeiro , como sugerido no post original, mas precisa de uma pequena correção, como observado por Alejandro:

//On the component
downloadfile(type: string){
  this.pservice.downloadfile(this.rundata.name, type)
      .subscribe(data => window.open(window.URL.createObjectURL(data)),
                  error => console.log("Error downloading the file."),
                  () => console.log('Completed file download.'));
  }

A segunda maneira seria usar o FileReader. A lógica é a mesma, mas podemos esperar explicitamente pelo FileReader carregar os dados, evitando o aninhamento e resolvendo o problema assíncrono.

//On the component using FileReader
downloadfile(type: string){
    var reader = new FileReader();
    this.pservice.downloadfile(this.rundata.name, type)
        .subscribe(res => reader.readAsDataURL(res), 
                    error => console.log("Error downloading the file."),
                    () => console.log('Completed file download.'));

    reader.onloadend = function (e) {
        window.open(reader.result, 'Excel', 'width=20,height=10,toolbar=0,menubar=0,scrollbars=no');
  }
}

Nota: Estou tentando baixar um arquivo do Excel e, mesmo que o download tenha sido acionado (portanto, isso responde à pergunta), o arquivo está corrompido. Veja a resposta a esta postagem para evitar o arquivo corrompido.

rll
fonte
7
Eu acho que a razão pela qual o arquivo é corrompido é porque você está carregando resno blob e realmente deseja res._body. No entanto, _bodyé uma variável privada e não acessível. A partir de hoje .blob()e .arrayBuffer()em um objeto de resposta http não foram implementados no Angular 2. text()e json()são as únicas duas opções, mas ambas irão alterar seu corpo. Você encontrou uma solução?
21316 # sschueller #
1
oi @rll, segui as etapas acima e estou recebendo a inscrição como concluída. Ainda não consegui ver o arquivo sendo baixado. Também não vi nenhum erro. Por favor ajude
AishApp
1
As 2 opções permitem baixar o arquivo, mas ele carrega os dados em segundo plano primeiro. E se eu tiver um arquivo grande que precise ser baixado?
f123 04/11
1
Minha solução é usar apenas <a href=""></a>para baixar um arquivo.
precisa saber é o seguinte
1
Eu sei que essa é uma resposta antiga, mas está alta nos resultados da pesquisa e é a resposta aceita: A linha `headers.append ('responseType', 'arraybuffer');` está errada. É uma opção, não um cabeçalho. Por favor, conserte. Aaaand ... Os cabeçalhos são criados e não utilizados. Não ajuda.
Stevo
17

Faça o download da solução * .zip do angular 2.4.x: você deve importar o ResponseContentType de '@ angular / http' e alterar o responseType para o ResponseContentType.ArrayBuffer (por padrão, ResponseContentType.Json)

getZip(path: string, params: URLSearchParams = new URLSearchParams()): Observable<any> {
 let headers = this.setHeaders({
      'Content-Type': 'application/zip',
      'Accept': 'application/zip'
    });

 return this.http.get(`${environment.apiUrl}${path}`, { 
   headers: headers, 
   search: params, 
   responseType: ResponseContentType.ArrayBuffer //magic
 })
          .catch(this.formatErrors)
          .map((res:Response) => res['_body']);
}
Alex Dzeiko
fonte
16

Para versões angulares mais recentes:

npm install file-saver --save
npm install @types/file-saver --save


import {saveAs} from 'file-saver/FileSaver';

this.http.get('endpoint/', {responseType: "blob", headers: {'Accept': 'application/pdf'}})
  .subscribe(blob => {
    saveAs(blob, 'download.pdf');
  });
Tobias Ernst
fonte
Obrigado, trabalha com o Angular 8. Não sei por que isso foi tão difícil de encontrar.
MDave
11

Baixar arquivos através do ajax é sempre um processo doloroso e, na minha opinião, é melhor permitir que o servidor e o navegador façam esse trabalho de negociação do tipo de conteúdo.

Eu acho que é melhor ter

<a href="api/sample/download"></a> 

para fazer isso. Isso nem exige nenhuma nova abertura de janelas e coisas assim.

O controlador MVC como na sua amostra pode ser como o abaixo:

[HttpGet("[action]")]
public async Task<FileContentResult> DownloadFile()
{
    // ...
    return File(dataStream.ToArray(), "text/plain", "myblob.txt");
}
Cerveja de gengibre
fonte
1
Você está certo, mas como gerenciar erros de servidor no aplicativo de página única? Em caso de erro, normalmente, um serviço REST retorna o JSON com o erro, resultando, assim, a aplicação para abrir o JSON em outra janela do navegador, o que não é o que o usuário quer ver
Luca
2
Se você tiver um token de acesso que você precisa fornecer isso não funciona
chris31389
Isso é claro e simples. Mas se você quiser fazer alguma autenticação, existe a possibilidade de ter algo como um token único. Portanto, em vez de tê-lo assim, você pode ter o URL como: exemplo.com/myuri/report?tokenid=1234-1233 E verificar o ID do token no banco de dados. É claro que não é um cenário simples e funciona em todas as situações, mas pode ser uma solução na situação em que, você tem acesso ao banco de dados antes de devolver o relatório como um fluxo ..
GingerBeer
Obtenha o URL de download do servidor. Assim, o servidor pode preparar o URL com um token de segurança único.
Jens Alenius # 23/17
8

Estou usando o Angular 4 com o objeto httpClient 4.3. Modifiquei uma resposta que encontrei no Blog técnico da Js, que cria um objeto de link, o usa para fazer o download e o destrói.

Cliente:

doDownload(id: number, contentType: string) {
    return this.http
        .get(this.downloadUrl + id.toString(), { headers: new HttpHeaders().append('Content-Type', contentType), responseType: 'blob', observe: 'body' })
}

downloadFile(id: number, contentType: string, filename:string)  {

    return this.doDownload(id, contentType).subscribe(  
        res => { 
            var url = window.URL.createObjectURL(res);
            var a = document.createElement('a');
            document.body.appendChild(a);
            a.setAttribute('style', 'display: none');
            a.href = url;
            a.download = filename;
            a.click();
            window.URL.revokeObjectURL(url);
            a.remove(); // remove the element
        }, error => {
            console.log('download error:', JSON.stringify(error));
        }, () => {
            console.log('Completed file download.')
        }); 

} 

O valor this.downloadUrl foi definido anteriormente para apontar para a API. Estou usando isso para baixar anexos, por isso sei o ID, contentType e nome do arquivo: Estou usando uma API MVC para retornar o arquivo:

 [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
    public FileContentResult GetAttachment(Int32 attachmentID)
    { 
        Attachment AT = filerep.GetAttachment(attachmentID);            
        if (AT != null)
        {
            return new FileContentResult(AT.FileBytes, AT.ContentType);  
        }
        else
        { 
            return null;
        } 
    } 

A classe de anexo fica assim:

 public class Attachment
{  
    public Int32 AttachmentID { get; set; }
    public string FileName { get; set; }
    public byte[] FileBytes { get; set; }
    public string ContentType { get; set; } 
}

O repositório filerep retorna o arquivo do banco de dados.

Espero que isso ajude alguém :)

davaus
fonte
7

Para quem usa Redux Pattern

Eu adicionei no protetor de arquivo como @Hector Cuevas nomeado em sua resposta. Usando o Angular2 v. 2.3.1, não precisei adicionar o @ types / file-saver.

O exemplo a seguir é baixar um diário como PDF.

As ações do diário

public static DOWNLOAD_JOURNALS = '[Journals] Download as PDF';
public downloadJournals(referenceId: string): Action {
 return {
   type: JournalActions.DOWNLOAD_JOURNALS,
   payload: { referenceId: referenceId }
 };
}

public static DOWNLOAD_JOURNALS_SUCCESS = '[Journals] Download as PDF Success';
public downloadJournalsSuccess(blob: Blob): Action {
 return {
   type: JournalActions.DOWNLOAD_JOURNALS_SUCCESS,
   payload: { blob: blob }
 };
}

Os efeitos do diário

@Effect() download$ = this.actions$
    .ofType(JournalActions.DOWNLOAD_JOURNALS)
    .switchMap(({payload}) =>
        this._journalApiService.downloadJournal(payload.referenceId)
        .map((blob) => this._actions.downloadJournalsSuccess(blob))
        .catch((err) => handleError(err, this._actions.downloadJournalsFail(err)))
    );

@Effect() downloadJournalSuccess$ = this.actions$
    .ofType(JournalActions.DOWNLOAD_JOURNALS_SUCCESS)
    .map(({payload}) => saveBlobAs(payload.blob, 'journal.pdf'))

O serviço de diário

public downloadJournal(referenceId: string): Observable<any> {
    const url = `${this._config.momentumApi}/api/journals/${referenceId}/download`;
    return this._http.getBlob(url);
}

O serviço HTTP

public getBlob = (url: string): Observable<any> => {
    return this.request({
        method: RequestMethod.Get,
        url: url,
        responseType: ResponseContentType.Blob
    });
};

O redutor de diário Embora isso defina apenas os estados corretos usados ​​em nosso aplicativo, eu ainda queria adicioná-lo para mostrar o padrão completo.

case JournalActions.DOWNLOAD_JOURNALS: {
  return Object.assign({}, state, <IJournalState>{ downloading: true, hasValidationErrors: false, errors: [] });
}

case JournalActions.DOWNLOAD_JOURNALS_SUCCESS: {
  return Object.assign({}, state, <IJournalState>{ downloading: false, hasValidationErrors: false, errors: [] });
}

Espero que isto seja útil.

Casper Nybroe
fonte
7

Partilho a solução que me ajudou (qualquer melhoria é muito apreciada)

No seu serviço 'pservice':

getMyFileFromBackend(typeName: string): Observable<any>{
    let param = new URLSearchParams();
    param.set('type', typeName);
    // setting 'responseType: 2' tells angular that you are loading an arraybuffer
    return this.http.get(http://MYSITE/API/FILEIMPORT, {search: params, responseType: 2})
            .map(res => res.text())
            .catch((error:any) => Observable.throw(error || 'Server error'));
}

Parte do componente :

downloadfile(type: string){
   this.pservice.getMyFileFromBackend(typename).subscribe(
                    res => this.extractData(res),
                    (error:any) => Observable.throw(error || 'Server error')
                );
}

extractData(res: string){
    // transforme response to blob
    let myBlob: Blob = new Blob([res], {type: 'application/vnd.oasis.opendocument.spreadsheet'}); // replace the type by whatever type is your response

    var fileURL = URL.createObjectURL(myBlob);
    // Cross your fingers at this point and pray whatever you're used to pray
    window.open(fileURL);
}

Na parte do componente, você chama o serviço sem assinar uma resposta. A inscrição para obter uma lista completa dos tipos de mímica do openOffice, consulte: http://www.openoffice.org/framework/documentation/mimetypes/mimetypes.html

Ismail H
fonte
7

Será melhor se você tentar chamar o novo método dentro de você subscribe

this._reportService.getReport()
    .subscribe((data: any) => {
        this.downloadFile(data);
    },
        (error: any) => сonsole.log(error),
        () => console.log('Complete')
    );

downloadFile(data)Função interna que precisamos fazerblock, link, href and file name

downloadFile(data: any, type: number, name: string) {
    const blob = new Blob([data], {type: 'text/csv'});
    const dataURL = window.URL.createObjectURL(blob);

    // IE doesn't allow using a blob object directly as link href
    // instead it is necessary to use msSaveOrOpenBlob
    if (window.navigator && window.navigator.msSaveOrOpenBlob) {
      window.navigator.msSaveOrOpenBlob(blob);
      return;
    }

    const link = document.createElement('a');
    link.href = dataURL;
    link.download = 'export file.csv';
    link.click();

    setTimeout(() => {

      // For Firefox it is necessary to delay revoking the ObjectURL
      window.URL.revokeObjectURL(dataURL);
      }, 100);
    }
}
Volodymyr Khmil
fonte
5

Para baixar e mostrar arquivos PDF , um código muito semelhante é o seguinte:

  private downloadFile(data: Response): void {
    let blob = new Blob([data.blob()], { type: "application/pdf" });
    let url = window.URL.createObjectURL(blob);
    window.open(url);
  }

  public showFile(fileEndpointPath: string): void {
    let reqOpt: RequestOptions = this.getAcmOptions();  //  getAcmOptions is our helper method. Change this line according to request headers you need.
    reqOpt.responseType = ResponseContentType.Blob;
    this.http
      .get(fileEndpointPath, reqOpt)
      .subscribe(
        data => this.downloadFile(data),
        error => alert("Error downloading file!"),
        () => console.log("OK!")
      );
  }
Baatar
fonte
5

Aqui está algo que eu fiz no meu caso -

// service method
downloadFiles(vendorName, fileName) {
    return this.http.get(this.appconstants.filesDownloadUrl, { params: { vendorName: vendorName, fileName: fileName }, responseType: 'arraybuffer' }).map((res: ArrayBuffer) => { return res; })
        .catch((error: any) => _throw('Server error: ' + error));
}

// a controller function which actually downloads the file
saveData(data, fileName) {
    var a = document.createElement("a");
    document.body.appendChild(a);
    a.style = "display: none";
    let blob = new Blob([data], { type: "octet/stream" }),
        url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = fileName;
    a.click();
    window.URL.revokeObjectURL(url);
}

// a controller function to be called on requesting a download
downloadFiles() {
    this.service.downloadFiles(this.vendorName, this.fileName).subscribe(data => this.saveData(data, this.fileName), error => console.log("Error downloading the file."),
        () => console.info("OK"));
}

A solução é referenciada aqui - aqui

Tushar Walzade
fonte
4

Atualize para a resposta de Hector usando o salvador de arquivos e o HttpClient para a etapa 2:

public downloadFile(file: File): Observable<Blob> {
    return this.http.get(file.fullPath, {responseType: 'blob'})
}
dmcgrandle
fonte
3

Eu obtive uma solução para baixar do angular 2 sem ficar corrompido, usando spring mvc e angular 2

1º - meu tipo de retorno é: - ResponseEntity do java end. Aqui estou enviando byte [] array tem tipo de retorno do controlador.

2º - para incluir o salvador de arquivos no seu espaço de trabalho - na página de índice como:

<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js"></script>

3º - no componente ts escreva este código:

import {ResponseContentType} from '@angular.core';

let headers = new Headers({ 'Content-Type': 'application/json', 'MyApp-Application' : 'AppName', 'Accept': 'application/pdf' });
        let options = new RequestOptions({ headers: headers, responseType: ResponseContentType.Blob });
            this.http
            .post('/project/test/export',
                    somevalue,options)
              .subscribe(data => {

                  var mediaType = 'application/vnd.ms-excel';
                  let blob: Blob = data.blob();
                    window['saveAs'](blob, 'sample.xls');

                });

Isso fornecerá o formato de arquivo xls. Se você quiser outros formatos, altere o tipo de mídia e o nome do arquivo com a extensão correta.

user2025069
fonte
3

Eu estava enfrentando esse mesmo caso hoje, tive que baixar um arquivo pdf como um anexo (o arquivo não deve ser renderizado no navegador, mas baixado). Para conseguir isso, descobri que era necessário obter o arquivo em um Angular Blobe, ao mesmo tempo, adicionar um Content-Dispositioncabeçalho na resposta.

Este foi o mais simples que pude obter (Angular 7):

Dentro do serviço:

getFile(id: String): Observable<HttpResponse<Blob>> {
  return this.http.get(`./file/${id}`, {responseType: 'blob', observe: 'response'});
}

Então, quando preciso fazer o download do arquivo em um componente, posso simplesmente:

fileService.getFile('123').subscribe((file: HttpResponse<Blob>) => window.location.href = file.url);

ATUALIZAR:

Removida a configuração desnecessária do cabeçalho do serviço

Gabriel Sezefredo
fonte
Se eu usar window.location.href em vez de window.open, o Chrome o tratará como vários downloads de arquivos.
DanO 13/06/19
isto não vai funcionar se você tem auth símbolo necessários no cabeçalho
garg10may
3

O código a seguir funcionou para mim

let link = document.createElement('a');
link.href = data.fileurl; //data is object received as response
link.download = data.fileurl.substr(data.fileurl.lastIndexOf('/') + 1);
link.click();
Manjericão
fonte
2

Até agora, encontrei as respostas que careciam de insight e avisos. Você pode e deve observar incompatibilidades com o IE10 + (se você se importa).

Este é o exemplo completo da parte do aplicativo e da parte do serviço depois. Observe que definimos a observação: "resposta" para capturar o cabeçalho do nome do arquivo. Observe também que o cabeçalho Content-Disposition deve ser definido e exposto pelo servidor; caso contrário, o atual Angular HttpClient não o passará. Adicionei um pedaço de código do dotnet para isso abaixo.

public exportAsExcelFile(dataId: InputData) {
    return this.http.get(this.apiUrl + `event/export/${event.id}`, {
        responseType: "blob",
        observe: "response"
    }).pipe(
        tap(response => {
            this.downloadFile(response.body, this.parseFilename(response.headers.get('Content-Disposition')));
        })
    );
}

private downloadFile(data: Blob, filename: string) {
    const blob = new Blob([data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8;'});
    if (navigator.msSaveBlob) { // IE 10+
        navigator.msSaveBlob(blob, filename);
    } else {
        const link = document.createElement('a');
        if (link.download !== undefined) {
            // Browsers that support HTML5 download attribute
            const url = URL.createObjectURL(blob);
            link.setAttribute('href', url);
            link.setAttribute('download', filename);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
}

private parseFilename(contentDisposition): string {
    if (!contentDisposition) return null;
    let matches = /filename="(.*?)"/g.exec(contentDisposition);

    return matches && matches.length > 1 ? matches[1] : null;
}

Núcleo Dotnet, com Content-Disposition & MediaType

 private object ConvertFileResponse(ExcelOutputDto excelOutput)
    {
        if (excelOutput != null)
        {
            ContentDisposition contentDisposition = new ContentDisposition
            {
                FileName = excelOutput.FileName.Contains(_excelExportService.XlsxExtension) ? excelOutput.FileName : "TeamsiteExport.xlsx",
                Inline = false
            };
            Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
            Response.Headers.Add("Content-Disposition", contentDisposition.ToString());
            return File(excelOutput.ExcelSheet, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        }
        else
        {
            throw new UserFriendlyException("The excel output was empty due to no events.");
        }
    }
David Zwart
fonte
1
 let headers = new Headers({
                'Content-Type': 'application/json',
                'MyApp-Application': 'AppName',
                'Accept': 'application/vnd.ms-excel'
            });
            let options = new RequestOptions({
                headers: headers,
                responseType: ResponseContentType.Blob
            });


this.http.post(this.urlName + '/services/exportNewUpc', localStorageValue, options)
                .subscribe(data => {
                    if (navigator.appVersion.toString().indexOf('.NET') > 0)
                    window.navigator.msSaveBlob(data.blob(), "Export_NewUPC-Items_" + this.selectedcategory + "_" + this.retailname +"_Report_"+this.myDate+".xlsx");

                    else {
                        var a = document.createElement("a");
                        a.href = URL.createObjectURL(data.blob());
                        a.download = "Export_NewUPC-Items_" + this.selectedcategory + "_" + this.retailname +"_Report_"+this.myDate+ ".xlsx";
                        a.click();
                    }
                    this.ui_loader = false;
                    this.selectedexport = 0;
                }, error => {
                    console.log(error.json());
                    this.ui_loader = false;
                    document.getElementById("exceptionerror").click();
                });
user2025069
fonte
1

Basta colocar o urlcomo hrefcomo abaixo.

<a href="my_url">Download File</a>
Harunur Rashid
fonte
Funciona? Eu recebo o erro ... "ERRO TypeError:" Acesso ao 'arquivo: ///Downloads/test.json' do script negado. ""
Jay
Obrigado, você pode compartilhar como é a aparência do seu URL? Id protocolo de arquivo ou http ou algo mais?
Jay
É um protocolo de arquivo.
Harunur Rashid
1

Você também pode fazer o download de um arquivo diretamente do seu modelo, no qual usa o atributo de download, e [attr.href]pode fornecer um valor de propriedade do componente. Esta solução simples deve funcionar na maioria dos navegadores.

<a download [attr.href]="yourDownloadLink"></a>

Referência: https://www.w3schools.com/tags/att_a_download.asp

Máx.
fonte
1
Bem-vindo ao SO! Verifique se minhas correções (de digitação e gramática) são úteis.
precisa saber é
0

Se você enviar apenas os parâmetros para um URL, poderá fazê-lo desta maneira:

downloadfile(runname: string, type: string): string {
   return window.location.href = `${this.files_api + this.title +"/"+ runname + "/?file="+ type}`;
}

no serviço que recebe os parâmetros

Joel Sulca Cordova
fonte
0

Esta resposta sugere que você não pode baixar arquivos diretamente com o AJAX, principalmente por razões de segurança. Então, vou descrever o que faço nessa situação,

01. Adicione hrefatributo na sua marca âncora dentro do component.htmlarquivo,
por exemplo: -

<div>
       <a [href]="fileUrl" mat-raised-button (click)='getGenaratedLetterTemplate(element)'> GENARATE </a>
</div>

02. Execute todas as etapas a seguir component.tspara contornar o nível de segurança e exibir a caixa de diálogo Salvar como pop-up,
por exemplo: -

import { environment } from 'environments/environment';
import { DomSanitizer } from '@angular/platform-browser';
export class ViewHrApprovalComponent implements OnInit {
private apiUrl = environment.apiUrl;
  fileUrl
 constructor(
    private sanitizer: DomSanitizer,
    private letterService: LetterService) {}
getGenaratedLetterTemplate(letter) {

    this.data.getGenaratedLetterTemplate(letter.letterId).subscribe(
      // cannot download files directly with AJAX, primarily for security reasons);
    console.log(this.apiUrl + 'getGeneratedLetter/' + letter.letterId);
    this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.apiUrl + 'getGeneratedLetter/' + letter.letterId);
  }

Nota: Esta resposta funcionará se você estiver recebendo um erro "OK" com o código de status 200

Indrajith Ekanayake
fonte
0

Bem, escrevi um código inspirado em muitas das respostas acima que devem funcionar facilmente na maioria dos cenários em que o servidor envia um arquivo com um cabeçalho de disposição de conteúdo, sem instalações de terceiros, exceto rxjs e angular.

Primeiro, como chamar o código do seu arquivo de componente

this.httpclient.get(
   `${myBackend}`,
   {
      observe: 'response',
      responseType: 'blob'
   }
).pipe(first())
.subscribe(response => SaveFileResponse(response, 'Custom File Name.extension'));

Como você pode ver, é basicamente a chamada média de back-end angular, com duas alterações

  1. Estou observando a resposta em vez do corpo
  2. Estou sendo explícito sobre a resposta ser um blob

Depois que o arquivo é buscado no servidor, em princípio, delegar toda a tarefa de salvar o arquivo na função auxiliar, que eu mantenho em um arquivo separado, e importar para o componente que eu precisar

export const SaveFileResponse = 
(response: HttpResponse<Blob>, 
 filename: string = null) => 
{
    //null-checks, just because :P
    if (response == null || response.body == null)
        return;

    let serverProvidesName: boolean = true;
    if (filename != null)
        serverProvidesName = false;

    //assuming the header is something like
    //content-disposition: attachment; filename=TestDownload.xlsx; filename*=UTF-8''TestDownload.xlsx
    if (serverProvidesName)
        try {
            let f: string = response.headers.get('content-disposition').split(';')[1];
            if (f.includes('filename='))
                filename = f.substring(10);
        }
        catch { }
    SaveFile(response.body, filename);
}

//Create an anchor element, attach file to it, and
//programmatically click it. 
export const SaveFile = (blobfile: Blob, filename: string = null) => {
    const a = document.createElement('a');
    a.href = window.URL.createObjectURL(blobfile);
    a.download = filename;
    a.click();
}

Não há mais nomes de arquivos GUID enigmáticos! Podemos usar qualquer nome que o servidor forneça, sem precisar especificá-lo explicitamente no cliente ou substituir o nome do arquivo fornecido pelo servidor (como neste exemplo). Além disso, é possível alterar facilmente, se necessário, o algoritmo de extração do nome do arquivo da disposição do conteúdo para atender às suas necessidades e tudo o mais permanecerá inalterado - no caso de um erro durante essa extração, ele passará apenas 'nulo' como o nome do arquivo.

Como outra resposta já apontada, o IE precisa de algum tratamento especial, como sempre. Mas com a borda do cromo chegando em alguns meses, não me preocuparia com isso ao criar novos aplicativos (espero). Há também a questão de revogar o URL, mas eu não tenho tanta certeza disso, por isso, se alguém pudesse ajudar nos comentários, isso seria incrível.

Dibyodyuti Mondal
fonte
0

Se uma guia abrir e fechar sem baixar nada, tentei seguir com link de âncora falso e funcionou.

downloadFile(x: any) {
var newBlob = new Blob([x], { type: "application/octet-stream" });

    // IE doesn't allow using a blob object directly as link href
    // instead it is necessary to use msSaveOrOpenBlob
    if (window.navigator && window.navigator.msSaveOrOpenBlob) {
      window.navigator.msSaveOrOpenBlob(newBlob);
      return;
    }

    // For other browsers: 
    // Create a link pointing to the ObjectURL containing the blob.
    const data = window.URL.createObjectURL(newBlob);

    var link = document.createElement('a');
    link.href = data;
    link.download = "mapped.xlsx";
    // this is necessary as link.click() does not work on the latest firefox
    link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));

    setTimeout(function () {
      // For Firefox it is necessary to delay revoking the ObjectURL
      window.URL.revokeObjectURL(data);
      link.remove();
    }, 100);  }
Damitha
fonte