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.
