javascript - Hur laddar jag ner en fil med Angular2 eller högre
Jag har en WebApi/MVC-app för vilken jag utvecklar en Angular2-klient (för att ersätta MVC). Jag har vissa problem med att förstå hur Angular sparar en fil.
Förfrågan är okej (fungerar bra med MVC, och vi kan logga den mottagna datan) men jag kan inte lista ut hur man sparar den nedladdade datan (jag följer mest samma logik som i detta inlägg). Jag är säker på att det är idiotiskt enkelt, men hittills förstår jag det helt enkelt inte.
Koden för komponentfunktionen finns nedan. Jag har försökt olika alternativ, blob-metoden borde vara rätt sätt att gå tillväga såvitt jag förstod, men det finns ingen funktion createObjectURL i URL. Jag kan inte ens hitta definitionen av URL i window, men uppenbarligen finns den. Om jag använder FileSaver.js-modulen får jag samma fel. Så jag antar att detta är något som har ändrats nyligen eller ännu inte är implementerat. Hur kan jag trigga fil-sparandet i 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);
}
Av fullständighetens skull är tjänsten som hämtar datan nedan, men det enda den gör är att utfärda förfrågan och vidarebefordra datan utan att mappa om den om det lyckas:
downloadfile(runname: string, type: string){
return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
.catch(this.logAndPassOn);
}
Svar #1
Problemet är att observabeln körs i en annan kontext, så när du försöker skapa URL-variabeln har du ett tomt objekt och inte den blob du vill ha.
Ett av de många sätt som finns för att lösa detta är som följer:
this._reportService.getReport().subscribe(data => this.downloadFile(data)),//console.log(data),
error => console.log('Error downloading the file.'),
() => console.info('OK');
När förfrågan är klar kommer den att kalla på funktionen "downloadFile" som definieras enligt följande:
downloadFile(data: Response) {
const blob = new Blob([data], { type: 'text/csv' });
const url= window.URL.createObjectURL(blob);
window.open(url);
}
blobben har skapats perfekt och så URL-variabeln, om den inte öppnar det nya fönstret vänligen kontrollera att du redan har importerat 'rxjs/Rx';
import 'rxjs/Rx' ;
Jag hoppas att detta kan hjälpa dig.
Svar #2
Prova detta!
1 - Installera beroenden för att visa spara/öppna fil-popup
npm install file-saver --save
npm install -D @types/file-saver
2- Skapa en tjänst med denna funktion för att ta emot data
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- I komponenten analysera blobben med 'file-saver'
import {saveAs as importedSaveAs} from "file-saver";
this.myService.downloadFile(this.id).subscribe(blob => {
importedSaveAs(blob, this.fileName);
}
)
Det här fungerar för mig!
Svar #3
Om du inte behöver lägga till headers i förfrågan, för att ladda ner en fil i Angular2 kan du göra en enkel (KISS-PRINCIPEN):
window.location.href='http://example.com/myuri/report?param=x';
i din komponent.
Svar #4
Vad sägs om detta?
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();
})
Jag skulle kunna använda det.
inget behov av ytterligare paket.
Svar #5
Detta är för personer som vill veta hur man gör det med HttpClient och file-saver:
- Installera file-saver
npm install file-saver --save
npm install @types/file-saver --save
API-tjänstklass:
export() {
return this.http.get(this.download_endpoint,
{responseType: 'blob'});
}
Komponent:
import { saveAs } from 'file-saver';
exportPdf() {
this.api_service.export().subscribe(data => saveAs(data, `pdf report.pdf`));
}
Svar #6
För nyare versioner av Angular:
npm install file-saver --save
npm install @types/file-saver --save
import {saveAs} from 'file-saver';
this.http.get('endpoint/', {responseType: "blob", headers: {'Accept': 'application/pdf'}})
.subscribe(blob => {
saveAs(blob, 'download.pdf');
});
Svar #7
Som nämnts av Alejandro Corredor är det ett enkelt scope-fel. subscribe körs asynkront och open måste placeras i det sammanhanget, så att datan är färdigladdad när vi utlöser nedladdningen.
Det sagt finns det två sätt att göra det på. Som dokumentationen rekommenderar tar tjänsten hand om att hämta och kartlägga datan:
//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);
}
Sedan, på komponenten prenumererar vi bara och hanterar de mappade uppgifterna. Det finns två möjligheter. Den första, som föreslogs i det ursprungliga inlägget, men behöver en liten korrigering som påpekats av 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.'));
}
Det andra sättet skulle vara att använda FileReader. Logiken är densamma men vi kan uttryckligen vänta på att FileReader laddar data, vilket undviker nästlingen och löser asynkronitetsproblemet.
//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');
}
}
Notera: Jag försöker ladda ner en Excel-fil, och även om nedladdningen utlöses (så detta besvarar frågan), är filen korrupt. Se svaret på detta inlägg för hur du undviker den korrupta filen.
Svar #8
Ladda ner *.zip-lösning för angular 2.4.x: du måste importera ResponseContentType från '@angular/http' och ändra responseType till ResponseContentType.ArrayBuffer (som standard är det 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']);
}
Svar #9
Jag använder Angular 4 med httpClient-objektet från 4.3. Jag modifierade ett svar jag hittade i Js tekniska blogg som skapar ett länkobjekt, använder det för att göra nedladdningen och sedan förstör det.
Klient :
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.')
});
}
Värdet på this.downloadUrl har tidigare satts för att peka på API:et. Jag använder detta för att ladda ner bilagor, så jag känner till id, contentType och filnamn: Jag använder ett MVC-API för att returnera filen:
[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;
}
}
Bilageklassen ser ut så här:
public class Attachment
{
public Int32 AttachmentID { get; set; }
public string FileName { get; set; }
public byte[] FileBytes { get; set; }
public string ContentType { get; set; }
}
Filerep-förrådet returnerar filen från databasen.
Hoppas detta hjälper någon :)
Svar #10
Att ladda ner filer genom ajax är alltid en smärtsam process och enligt min åsikt är det bäst att låta servern och webbläsaren göra detta arbete med innehållstypförhandling.
Jag tror att det är bäst att ha
<a href="api/sample/download"></a>
att göra det. Detta kräver inte ens att några nya fönster öppnas och sådant.
MVC-kontrollern som i ditt exempel kan vara som den nedan:
[HttpGet("[action]")]
public async Task<FileContentResult> DownloadFile()
{
// ...
return File(dataStream.ToArray(), "text/plain", "myblob.txt");
}
Svar #11
Det kommer att vara bättre om du försöker kalla den nya metoden inuti din subscribe
this._reportService.getReport()
.subscribe((data: any) => {
this.downloadFile(data);
},
(error: any) => сonsole.log(error),
() => console.log('Complete')
);
Inuti downloadFile(data)-funktionen behöver vi skapa block, länk, href och filnamn
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);
}
}
