Búsqueda de sitios web

typescript - Crear archivo binario en Angular


Tengo una aplicación Angular donde necesito obtener el contenido de un archivo desde una API REST y luego generar un archivo en el cliente.

Como no puedo escribir un archivo nuevo en el cliente, uso esta pregunta como una solución alternativa.

Así que básicamente lo que hace es crear un Blob con los contenidos y generar un enlace de descarga, y luego simula un clic en el enlace para que el documento se descargue.

Este es mi código:

download(filename) {
    this.service
      .downloadFile(filename)
      .subscribe(data => {
        console.log(data.document);
        this.downloadFile(data.document, filename);
      }, err => this.info = err);
  }

esto obtiene los datos del servidor

private downloadFile(content: string, filename: string) {
    const link = document.createElement('a');
    link.setAttribute('download', filename);
    link.href = this.makeTextFileUrl(content, filename);

    document.body.appendChild(link);

    // wait for the link to be added to the document
    window.requestAnimationFrame(function () {
      const event = new MouseEvent('click');
      link.dispatchEvent(event);
      document.body.removeChild(link);
    });
  }

esto construye el enlace y simula el clic para descargar

private makeTextFileUrl(content: string, filename: string): string {
    let url = null;

    const mime = this.getMimeType(filename);
    console.log(mime);
    const blob = new Blob([content], { type: mime });
    // If we are replacing a previously generated file we need to
    // manually revoke the object URL to avoid memory leaks.
    if (url !== null) {
      window.URL.revokeObjectURL(url);
    }

    url = window.URL.createObjectURL(blob);
    return url;
  }

esto genera el enlace URL

private getMimeType(filename: string): string {
    if (filename.indexOf('.docx') !== -1) return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
    if (filename.indexOf('.doc') !== -1) return 'application/msword';
    if (filename.indexOf('.pdf') !== -1) return 'application/pdf';
    return 'text/plain';
  }

el tipo MIME es diferente dependiendo de la extensión

Entonces, esto funciona para archivos de texto plano, pero con archivos docx obtengo un error cuando intento abrir el archivo y con pdf el archivo parece estar en blanco.

Leí que para archivos binarios necesito configurar el responseType en ArrayBuffer para que esto funcione, así que actualicé mi llamada get del servicio a esto

downloadFile(id: string): Observable<any> {
    return this.authHttp
      .get(`${this.api}/download/${id}`, { responseType: ResponseContentType.ArrayBuffer })
      .map(res => {
        const data = res.json();
        return data;
      });
  }

pero ahora obtengo un error cuando intento descargar el archivo, sin importar si es un archivo txt, pdf o docx SyntaxError: Unexpected token ≻ in JSON at position 0

¿Alguien sabe cómo solucionar esto?

Gracias

Respuesta #1

Así que finalmente logré que esto funcionara.

Resulta que estaba usando una API Rest intermedia para obtener el contenido del archivo y luego reenviarlo a mi aplicación frontend, pero en ese paso el cuerpo estaba siendo analizado como una cadena, por lo que lo que obtenía en mi aplicación frontend no era el contenido original.

En este hilo está la solución a ese problema. Reenvía el cuerpo de la respuesta exactamente como viene

Entonces solo tuve que cambiar algunas cosas en mi aplicación frontend para obtener el archivo binario.

downloadFile(id: string): Observable<any> {
    return this.authHttp
      .get(`${this.api}/${id}`, { responseType: ResponseContentType.ArrayBuffer })
      .map(res => {
        return res.blob();
      });
  }

Así que ahora estoy recuperando los datos no como un json sino como un blob.

Entonces en mi componente

download(filename) {
    const id = this.filesIds[filename];
    this.service
      .downloadFile(id)
      .subscribe(blob => {
        this.downloadFile(blob, filename);
      }, err => this.info = err);
  }

  private downloadFile(blob: Blob, filename: string) {
    const link = document.createElement('a');
    link.setAttribute('download', filename);

    link.href = this.makeTextFileUrl(blob, filename);

    document.body.appendChild(link);

    // wait for the link to be added to the document
    window.requestAnimationFrame(function () {
      const event = new MouseEvent('click');
      link.dispatchEvent(event);
      document.body.removeChild(link);
    });
  }

El resto permanece igual.