¿Cómo descargo un archivo con Angular2 o superior?

182

Tengo una aplicación WebApi / MVC para la que estoy desarrollando un cliente angular2 (para reemplazar MVC). Tengo problemas para entender cómo Angular guarda un archivo.

La solicitud está bien (funciona bien con MVC, y podemos registrar los datos recibidos) pero no puedo encontrar la manera de guardar los datos descargados (principalmente sigo la misma lógica que en esta publicación ). Estoy seguro de que es estúpidamente simple, pero hasta ahora simplemente no lo entiendo.

El código de la función componente está debajo. He probado diferentes alternativas, la forma de blob debe ser el camino a seguir por lo que he entendido, pero no hay ninguna función createObjectURLen URL. Ni siquiera puedo encontrar la definición de URLen la ventana, pero aparentemente existe. Si uso el FileSaver.jsmódulo me sale el mismo error. Así que supongo que esto es algo que cambió recientemente o aún no se ha implementado. ¿Cómo puedo activar el archivo guardado en 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);
}

En aras de la exhaustividad, el servicio que recupera los datos está debajo, pero lo único que hace es emitir la solicitud y pasar los datos sin mapear si tiene éxito:

downloadfile(runname: string, type: string){
   return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
            .catch(this.logAndPassOn);
}
rll
fuente
No puede descargar archivos grandes con este método. Llegará al límite de memoria por pestaña. Esto puede ser tan bajo como 1-2GB.
Matthew B.
@MatthewB. Ojalá hubieras dicho lo que era mejor.
Steve
Para descargas de archivos grandes, debe especificar una nueva pestaña, por ejemplo, si simula un clic <A>, el objetivo debe ser igual a "_blank" o enviar un formulario. No creo que haya una forma limpia de sortear la limitación de tamaño de archivo grande con solicitudes de estilo Ajax.
Matthew B.

Respuestas:

181

El problema es que el observable se ejecuta en otro contexto, por lo que cuando intentas crear la URL var, tienes un objeto vacío y no el blob que deseas.

Una de las muchas formas que existen para resolver esto es la siguiente:

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

Cuando la solicitud esté lista, llamará a la función "downloadFile" que se define de la siguiente manera:

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

el blob se ha creado perfectamente y, por lo tanto, la URL var, si no abre la nueva ventana, compruebe que ya ha importado 'rxjs / Rx';

  import 'rxjs/Rx' ;

Espero que esto pueda ayudarte.

Alejandro Corredor
fuente
9
¿Qué es this._reportService.getReport()y qué devuelve?
Burjua
3
@Burjua the getReport()regresa athis.http.get(PriceConf.download.url)
ji-ruh
66
El problema que tengo es que la ventana se abre y se cierra inmediatamente sin descargar el archivo
Braden Brown
77
¿Cómo podemos establecer el nombre del archivo aquí? por defecto, elige un valor numérico como nombre
Saurabh
8
He usado el código anterior para descargar un archivo desde la respuesta de la API, pero recibo un error al crear la parte Blob "La respuesta de tipo no es asignable para escribir Blobpart". Ayuda amablemente si alguien conoce este problema
knbibin
92

Prueba esto !

1 - Instalar dependencias para mostrar guardar / abrir archivo emergente

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

2- Crea un servicio con esta función para recibir los datos

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- En el componente, analiza el blob con 'file-saver'

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

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

Esto funciona para mi!

Hector cuevas
fuente
1
Utilicé el paso 2 en combinación con la respuesta de @Alejandro y funcionó sin la necesidad de instalar el protector de archivos ...
Ewert
55
¡Gracias! Funciona perfectamente! Me pregunto si podemos obtener el nombre de archivo que se define en el encabezado de la respuesta. ¿Es eso posible?
jfajunior
2
error Av5 El argumento del tipo 'RequestOptions' no se puede asignar al parámetro del tipo '{encabezados ?: HttpHeaders | {[encabezado: cadena]: cadena | cuerda[]; };
giveJob
Sin embargo, este no es adecuado para la descarga de archivos grandes.
Reven
61

Si no es necesario agregar encabezados en la solicitud, para descargar un archivo en Angular2 se puede hacer una sencilla :

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

en su componente

surfealokesea
fuente
44
¿Alguien puede decir por qué esta respuesta es rechazada? El tema es descargar un archivo usando angular2. Si este método funciona para hacer una descarga simple, también debe marcarse como una respuesta válida.
Saurabh Shetty
55
@SaurabhShetty, esto no ayudará en caso de que desee enviar encabezados personalizados, ¿qué sucede si desea enviar un token de autenticación, por ejemplo? ¡Si miras la pregunta OP, puedes ver que él usa authHttp!
A.Akram
66
Entiendo los votos negativos, sin embargo, esta respuesta resolvió mi problema.
JoeriShoeby
1
Si deja que el servidor devuelva la url en algún contexto, el servidor podría preparar la url. ej .: objeto: MyRecord.Cover. La portada podría ser una url para una imagen en el servidor. Al llamar a get (Myrecord), deja que el servidor devuelva la url preparada (Cover), con el token de seguridad y otros encabezados configurados.
Jens Alenius
2
Es una respuesta que funciona. Solo porque no tiene <insertar característica útil aquí> que no hace que no sea una respuesta.
gburton
47

Esto es para personas que buscan cómo hacerlo usando HttpClient y File-Saver:

  1. Instalar protector de archivos

npm install file-saver --save

npm install @ types / file-saver --save

Clase de servicio 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
fuente
1
¿Cómo mostrar el tamaño del archivo en el navegador cuando comienza la descarga? Estoy enviando el tamaño del archivo como longitud de contenido en el encabezado http.
hummbleCoder
35

¿Qué tal esto?

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

Yo podría hacer con eso.
No necesita paquete adicional.

harufumi.abe
fuente
3
Tan simple pero es el que funciona perfectamente. No abarrota el DOM, no crea ningún elemento. Combiné esta solución con algunos de los anteriores y funciona de maravilla.
Chax
20

Como mencionó Alejandro Corredor , es un simple error de alcance. Se subscribeejecuta de forma asincrónica y opendebe colocarse en ese contexto, de modo que los datos terminen de cargarse cuando activemos la descarga.

Dicho esto, hay dos formas de hacerlo. Como los documentos recomiendan, el servicio se encarga de obtener y asignar los datos:

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

Luego, en el componente solo nos suscribimos y tratamos con los datos mapeados. Hay dos posibilidades El primero , como se sugiere en la publicación original, pero necesita una pequeña corrección como señaló 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.'));
  }

La segunda forma sería usar FileReader. La lógica es la misma, pero podemos esperar explícitamente a que FileReader cargue los datos, evitando el anidamiento y resolviendo el problema así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: Estoy tratando de descargar un archivo de Excel, y aunque la descarga se activa (por lo que esto responde a la pregunta), el archivo está dañado. Vea la respuesta a esta publicación para evitar el archivo corrupto.

rll
fuente
77
Creo que la razón por la que el archivo se corrompe es porque está cargando resen el blob y realmente lo desea res._body. Sin embargo, _bodyes una variable privada y no accesible. A partir de hoy .blob()y .arrayBuffer()en un objeto de respuesta http no se han implementado en Angular 2. text()y json()son las únicas dos opciones, pero ambas van a confundir su cuerpo. ¿Has encontrado una solución?
sschueller
1
hola @rll, seguí los pasos anteriores y me estoy suscribiendo como completado. Aún así, no pude ver cómo se descargaba el archivo. No pude ver ningún error también. Por favor ayuda
AishApp
1
Las 2 opciones me permiten descargar el archivo, pero primero carga los datos en segundo plano. ¿Qué sucede si tengo un archivo grande que debe descargarse?
f123
1
Mi solución es simplemente usar <a href=""></a>para descargar un archivo.
user2061057
1
Sé que esta es una respuesta anterior, pero está en lo alto de los resultados de búsqueda y es la respuesta aceptada: la línea `headers.append ('responseType', 'arraybuffer');` está equivocada. Es una opción, no un encabezado. Por favor, arreglalo. Aaaand ... Los encabezados se crean y no se usan. No es útil.
Stevo
17

Descargue la solución * .zip para angular 2.4.x: debe importar ResponseContentType de '@ angular / http' y cambiar responseType a ResponseContentType.ArrayBuffer (de forma predeterminada, 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
fuente
16

Para versiones angulares más nuevas:

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
fuente
Gracias, funciona con Angular 8. No sé por qué fue tan difícil de encontrar.
MDave
11

La descarga de archivos a través de ajax siempre es un proceso doloroso y, en mi opinión, es mejor dejar que el servidor y el navegador hagan este trabajo de negociación de tipo de contenido.

Creo que es mejor tener

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

para hacerlo. Esto ni siquiera requiere que se abran ventanas nuevas y cosas así.

El controlador MVC como en su muestra puede ser como el siguiente:

[HttpGet("[action]")]
public async Task<FileContentResult> DownloadFile()
{
    // ...
    return File(dataStream.ToArray(), "text/plain", "myblob.txt");
}
Cerveza de jengibre
fuente
1
Tienes razón, pero ¿cómo puedes gestionar los errores del servidor dentro de la aplicación de una sola página? En caso de error, por lo general, un servicio REST devuelve el JSON con el error, lo que resulta de la aplicación para abrir el JSON en otra ventana del navegador, que no es lo que el usuario quiere ver
Luca
2
Si tiene un token de acceso, debe proporcionar que esto no funciona
chris31389
Esto es claro y simple. Pero si desea realizar alguna autenticación, existe la posibilidad de tener algo como un token de una sola vez. Entonces, en lugar de tenerlo así, puede tener la URL como: example.com/myuri/report?tokenid=1234-1233 y verificar la identificación del token en la base de datos. Por supuesto que no es un escenario y trabaja en todas las situaciones simples, pero pueden ser una solución en la situación en la que, se tiene acceso a la base de datos antes de devolver el informe como una corriente ..
Gingerbeer
Obtenga la URL de descarga del servidor. Por lo tanto, el servidor puede preparar la url con un token de seguridad.
Jens Alenius
8

Estoy usando Angular 4 con el objeto 4.3 httpClient. Modifiqué una respuesta que encontré en el Blog técnico de Js que crea un objeto de enlace, lo usa para realizar la descarga y luego lo destruye.

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

} 

El valor de this.downloadUrl se ha configurado previamente para que apunte a la API. Estoy usando esto para descargar archivos adjuntos, así que sé la identificación, el tipo de contenido y el nombre de archivo: estoy usando una API MVC para devolver el archivo:

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

La clase de archivo adjunto se ve así:

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

El repositorio de filerep devuelve el archivo de la base de datos.

Espero que esto ayude a alguien :)

davaus
fuente
7

Para aquellos que usan Redux Pattern

Agregué en el protector de archivos como @Hector Cuevas nombrado en su respuesta. Usando Angular2 v. 2.3.1, no necesitaba agregar el @ types / file-saver.

El siguiente ejemplo es descargar una revista como PDF.

Las acciones del diario

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

Los efectos del diario

@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'))

El servicio de revista

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

El servicio HTTP

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

El reductor de diario Aunque esto solo establece los estados correctos utilizados en nuestra aplicación, todavía quería agregarlo para mostrar el patrón 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 esto sea útil.

Casper Nybroe
fuente
7

Comparto la solución que me ayudó (cualquier mejora es muy apreciada)

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

En la parte componente, llama al servicio sin suscribirse a una respuesta. La suscripción para obtener una lista completa de los tipos de mimo de openOffice, consulte: http://www.openoffice.org/framework/documentation/mimetypes/mimetypes.html

Ismail H
fuente
7

Será mejor si intentas llamar al nuevo método dentro de ti subscribe

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

downloadFile(data)Función interior que necesitamos hacerblock, 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
fuente
5

Para descargar y mostrar archivos PDF , un código recortado muy similar es el siguiente:

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

Aquí hay algo que hice en mi 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"));
}

Se hace referencia a la solución desde aquí.

Tusza Walzade
fuente
4

Actualice la respuesta de Héctor utilizando el protector de archivos y HttpClient para el paso 2:

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

Obtuve una solución para descargar desde angular 2 sin corromper, usando spring mvc y angular 2

Primero, mi tipo de devolución es: - ResponseEntity from java end. Aquí estoy enviando byte [] array tiene tipo de retorno desde el controlador.

2º- para incluir el protector de archivos en su espacio de trabajo, en la página de índice como:

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

3º- en el componente ts escriba 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');

                });

Esto le dará el formato de archivo xls. Si desea otros formatos, cambie el tipo de medio y el nombre del archivo con la extensión correcta.

user2025069
fuente
3

Enfrenté este mismo caso hoy, tuve que descargar un archivo pdf como archivo adjunto (el archivo no debería mostrarse en el navegador, sino descargarse). Para lograr eso descubrí que tenía que obtener el archivo en un Angular Bloby, al mismo tiempo, agregar un Content-Dispositionencabezado en la respuesta.

Esto fue lo más simple que pude obtener (Angular 7):

Dentro del servicio:

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

Luego, cuando necesito descargar el archivo en un componente, simplemente puedo:

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

ACTUALIZAR:

Se eliminó la configuración innecesaria del encabezado del servicio

Gabriel Sezefredo
fuente
Si uso window.location.href en lugar de window.open, Chrome lo trata como descargas de múltiples archivos.
DanO
esto no funcionará si necesita token de autenticación en el encabezado
garg10may
3

El siguiente código me funcionó

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();
Albahaca
fuente
2

Las respuestas que encontré hasta ahora carecían de perspicacia y advertencias. Podrías y deberías estar atento a incompatibilidades con IE10 + (si te importa).

Este es el ejemplo completo con la parte de aplicación y la parte de servicio posterior. Tenga en cuenta que configuramos la observación: "respuesta" para capturar el encabezado del nombre de archivo. Tenga en cuenta también que el encabezado Content-Disposition debe ser configurado y expuesto por el servidor, de lo contrario, el Angular HttpClient actual no lo transmitirá. Agregué un código de dotnet core para eso a continuación.

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

Dotnet core, con Content-Disposition y 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
fuente
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
fuente
1

En pocas palabras el urlcomo hrefde la siguiente manera.

<a href="my_url">Download File</a>
Harunur Rashid
fuente
¿Funciona? Me sale el error ... "ERROR TypeError:" Acceso al 'archivo: ///Downloads/test.json' desde el script denegado ""
Jay
Gracias, ¿puedes compartir cómo se ve tu url? ¿Id archivo de protocolo o http o algo más?
Jay
Es el protocolo de archivo.
Harunur Rashid
1

También puede descargar un archivo directamente desde su plantilla donde utiliza el atributo de descarga y [attr.href]puede proporcionar un valor de propiedad del componente. Esta solución simple debería funcionar en la mayoría de los navegadores.

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

Referencia: https://www.w3schools.com/tags/att_a_download.asp

Max
fuente
1
Bienvenido a SO! Verifique si mis correcciones (tipografía y gramática) son útiles.
B - rian
0

Si solo envía los parámetros a una URL, puede hacerlo de esta manera:

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

en el servicio que recibe los parámetros

Joel Sulca Cordova
fuente
0

Esta respuesta sugiere que no puede descargar archivos directamente con AJAX, principalmente por razones de seguridad. Así que describiré lo que hago en esta situación,

01. Agregue hrefatributo en su etiqueta de anclaje dentro del component.htmlarchivo,
por ejemplo: -

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

02. Realice todos los pasos siguientes component.tspara evitar el nivel de seguridad y abrir el cuadro de diálogo emergente Guardar como,
por ejemplo: -

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 respuesta funcionará si recibe un error "OK" con el código de estado 200

Indrajith Ekanayake
fuente
0

Bueno, escribí un código inspirado en muchas de las respuestas anteriores que deberían funcionar fácilmente en la mayoría de los escenarios en los que el servidor envía un archivo con un encabezado de disposición de contenido, sin ninguna instalación de terceros, excepto rxjs y angular.

Primero, cómo llamar al código desde su archivo componente

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

Como puede ver, es básicamente la llamada de backend promedio de angular, con dos cambios

  1. Estoy observando la respuesta en lugar del cuerpo.
  2. Estoy siendo explícito acerca de que la respuesta es una gota

Una vez que el archivo se obtiene del servidor, en principio, delego toda la tarea de guardar el archivo en la función auxiliar, que guardo en un archivo separado, e importarlo a cualquier componente que necesite

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

¡Ya no hay nombres de archivos GUID crípticos! Podemos usar cualquier nombre que proporcione el servidor, sin tener que especificarlo explícitamente en el cliente, o sobrescribir el nombre de archivo proporcionado por el servidor (como en este ejemplo). Además, uno puede fácilmente, si es necesario, cambiar el algoritmo de extracción del nombre de archivo de la disposición de contenido para satisfacer sus necesidades, y todo lo demás no se verá afectado; en caso de error durante dicha extracción, simplemente pasará 'nulo' como el nombre del archivo.

Como otra respuesta ya señaló, IE necesita un tratamiento especial, como siempre. Pero con el borde de cromo en unos meses, no me preocuparía por eso mientras construyo nuevas aplicaciones (con suerte). También está la cuestión de revocar la URL, pero no estoy tan seguro de eso, así que si alguien pudiera ayudar con eso en los comentarios, sería increíble.

Dibyodyuti Mondal
fuente
0

Si una pestaña se abre y se cierra sin descargar nada, intenté seguir con el enlace de anclaje falso y funcionó.

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
fuente