Jak pobrać plik za pomocą Angular2 lub nowszego


182

Mam aplikację WebApi / MVC, dla której tworzę klienta angular2 (w celu zastąpienia MVC). Mam pewne problemy ze zrozumieniem, jak Angular zapisuje plik.

Żądanie jest w porządku (działa dobrze z MVC i możemy zarejestrować otrzymane dane), ale nie mogę dowiedzieć się, jak zapisać pobrane dane (głównie kieruję się tą samą logiką, co w tym poście ). Jestem pewien, że jest to głupio proste, ale jak dotąd po prostu tego nie rozumiem.

Kod funkcji komponentu znajduje się poniżej. Próbowałem różnych alternatyw, tak kropelka powinna być droga, iść tak daleko, jak to rozumieć, ale nie ma funkcji createObjectURLw URL. Nie mogę nawet znaleźć definicji URLw oknie, ale najwyraźniej istnieje. Jeśli używam FileSaver.jsmodułu, pojawia się ten sam błąd. Myślę więc, że jest to coś, co ostatnio się zmieniło lub nie zostało jeszcze wdrożone. Jak mogę wyzwolić zapis pliku w 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);
}

Ze względu na kompletność usługa, która pobiera dane, znajduje się poniżej, ale jedyne, co robi, to wysłanie żądania i przekazanie danych bez mapowania, jeśli się powiedzie:

downloadfile(runname: string, type: string){
   return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
            .catch(this.logAndPassOn);
}

Tą metodą nie można pobierać dużych plików. Osiągniesz limit pamięci na kartę. Może to być zaledwie 1-2 GB.
Matthew B.

@MatthewB. Szkoda, że ​​nie powiedziałeś, co było lepsze.
steve

W przypadku pobierania dużych plików należy określić nową kartę, np. W przypadku symulacji kliknięcia <A> cel musi być równy „_blank”. Lub prześlij formularz. Nie sądzę, aby istniał czysty sposób na obejście ograniczenia dużego rozmiaru pliku za pomocą żądań w stylu Ajax.
Matthew B.

Odpowiedzi:


181

Problem polega na tym, że obserwowalne działa w innym kontekście, więc kiedy próbujesz utworzyć zmienną URL, masz pusty obiekt, a nie żądany obiekt blob.

Jeden z wielu istniejących sposobów rozwiązania tego problemu jest następujący:

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

Gdy żądanie będzie gotowe, wywoła funkcję „downloadFile” zdefiniowaną w następujący sposób:

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

obiekt blob został stworzony idealnie, więc adres URL var, jeśli nie otwiera nowego okna, sprawdź, czy już zaimportowałeś 'rxjs / Rx';

  import 'rxjs/Rx' ;

Mam nadzieję, że to ci pomoże.


9
Co to jest this._reportService.getReport()i co wraca?
Burjua

3
@Burjua getReport()zwracathis.http.get(PriceConf.download.url)
ji-ruh

6
Problem, który mam, polega na tym, że okno otwiera się i zamyka natychmiast bez pobierania pliku
Braden Brown

7
Jak możemy tutaj ustawić nazwę pliku? domyślnie wybiera wartość liczbową jako nazwę
Saurabh

8
Użyłem powyższego kodu do pobrania pliku z odpowiedzi API, ale pojawia się błąd podczas tworzenia części Blob „Typ odpowiedzi nie można przypisać do typu Blobpart”. Prosimy o pomoc, jeśli ktoś zna ten problem
knbibin,

92

Spróbuj tego !

1 - Zainstaluj zależności dla wyskakującego okienka zapisz / otwórz plik

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

2- Utwórz usługę z tą funkcją, aby otrzymać dane

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- W komponencie przeanalizuj obiekt blob za pomocą „file-saver”

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

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

To działa dla mnie!


1
Użyłem kroku 2 w połączeniu z odpowiedzią od @Alejandro i zadziałało bez konieczności instalowania wygaszacza plików ...
Ewert

5
Dziękuję Ci! Działa idealnie! Zastanawiam się, czy uda nam się uzyskać nazwę pliku zdefiniowaną w nagłówku odpowiedzi. Czy to jest możliwe?
jfajunior

2
błąd Av5 Argumentu typu „RequestOptions” nie można przypisać do parametru typu „{headers ?: HttpHeaders | {[nagłówek: ciąg]: ciąg | strunowy[]; };
GiveJob

Ten jednak nie nadaje się do pobierania dużych plików.
Reven

61

Jeśli nie musisz dodawać nagłówków w żądaniu, aby pobrać plik w Angular2, możesz wykonać proste :

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

w swoim komponencie.


4
Czy ktoś może powiedzieć, dlaczego ta odpowiedź została odrzucona? Tematem jest pobranie pliku za pomocą angular2. Jeśli ta metoda działa w przypadku prostego pobierania, należy ją również zaznaczyć jako prawidłową odpowiedź.
Saurabh Shetty

5
@SaurabhShetty, To nie pomoże, jeśli chcesz wysłać niestandardowe nagłówki, a co jeśli chcesz na przykład wysłać token autoryzacji? Jeśli spojrzysz na pytanie OP, zobaczysz, że używa authHttp!
A. Akram

6
Rozumiem głosy przeciwne, niemniej jednak ta odpowiedź rozwiązała mój problem.
JoeriShoeby

1
Jeśli pozwolisz serwerowi zwrócić adres URL w jakimś kontekście, serwer może przygotować adres URL. np. obiekt: MyRecord.Cover. Okładka może być adresem URL do obrazu na serwerze. Wywołując get (Myrecord), pozwalasz serwerowi zwrócić przygotowany adres URL (Cover), z ustawionym tokenem bezpieczeństwa i innymi nagłówkami.
Jens Alenius

2
To odpowiedź, która działa. Tylko dlatego, że nie ma <wstaw tutaj użytecznej funkcji>, która nie czyni tego rozwiązaniem.
gburton

47

To jest dla osób, które chcą to zrobić przy użyciu HttpClient i funkcji oszczędzania plików:

  1. Zainstaluj oszczędzanie plików

npm install file-saver --save

npm install @ types / file-saver --save

Klasa usługi API:

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

Składnik:

import { saveAs } from 'file-saver';
exportPdf() {
    this.api_service.export().subscribe(data => saveAs(data, `pdf report.pdf`));
}

1
Jak wyświetlić rozmiar pliku w przeglądarce po rozpoczęciu pobierania? Wysyłam rozmiar pliku jako długość treści w nagłówku http.
humbleCoder,

35

Co powiesz na to?

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

Mógłbym z tym zrobić.
nie ma potrzeby dodatkowego pakietu.


3
Tak proste, ale to działa bezbłędnie. Nie zaśmieca DOM, nie tworzy żadnego elementu. Połączyłem to rozwiązanie z niektórymi powyższymi i działa jak urok.
Chax

20

Jak wspomniał Alejandro Corredor , jest to prosty błąd zakresu. subscribeProwadzony jest asynchronicznie i openmusi być umieszczony w tym kontekście, tak że dane zakończeniu ładowania kiedy wyzwolić pobieranie.

To powiedziawszy, można to zrobić na dwa sposoby. Zgodnie z dokumentacją usługa zajmuje się pobieraniem i mapowaniem danych:

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

Następnie w komponencie po prostu subskrybujemy i zajmujemy się zmapowanymi danymi. Są dwie możliwości. Pierwszy , jak sugerował w oryginalnym poście, ale wymaga niewielkiej korekty, jak zauważył 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.'));
  }

Drugim sposobem byłoby użycie FileReader. Logika jest taka sama, ale możemy jawnie poczekać, aż FileReader załaduje dane, unikając zagnieżdżania i rozwiązując problem asynchroniczny.

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

Uwaga: próbuję pobrać plik programu Excel i mimo że pobieranie zostało uruchomione (więc to odpowiada na pytanie), plik jest uszkodzony. Zobacz odpowiedź na ten post, aby uniknąć uszkodzenia pliku.


7
Myślę, że przyczyną uszkodzenia pliku jest to, że ładujesz resdo obiektu blob i faktycznie chcesz res._body. Jednak _bodyjest to zmienna prywatna i niedostępna. Na dzień dzisiejszy .blob()i .arrayBuffer()obiekt odpowiedzi http nie został zaimplementowany w Angular 2. text()i json()są to jedyne dwie opcje, ale obie mogą zniekształcić twoje ciało. Znalazłeś rozwiązanie?
sschueller

1
cześć @rll, wykonałem powyższe kroki i otrzymuję subskrypcję jako ukończoną. Nadal nie widziałem pobierania pliku. Nie widziałem również żadnego błędu. Proszę o pomoc
AishApp

1
Dwie opcje pozwalają mi pobrać plik, ale najpierw ładuje dane w tle. A jeśli mam duży plik, który należy pobrać?
f123

1
Moje rozwiązanie polega na tym, że wystarczy <a href=""></a>pobrać plik.
user2061057

1
Wiem, że to stara odpowiedź, ale jest wysoko w wynikach wyszukiwania i jest akceptowaną odpowiedzią: wiersz `headers.append ('responseType', 'arraybuffer');` jest błędny. To opcja, a nie nagłówek. Proszę napraw to. Aaaand ... Nagłówki są tworzone i nie są używane. Niepomocne.
Stevo

17

Pobierz rozwiązanie * .zip dla angular 2.4.x: musisz zaimportować ResponseContentType z '@ angular / http' i zmienić responseType na ResponseContentType.ArrayBuffer (domyślnie 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']);
}

16

W przypadku nowszych wersji kątowych:

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

Dzięki, działa z Angular 8. Nie wiem, dlaczego było to tak trudne do znalezienia.
MDave

11

Pobieranie pliku przez Ajax jest zawsze bolesnym procesem i moim zdaniem najlepiej jest pozwolić serwerowi i przeglądarce na negocjację typu treści.

Myślę, że najlepiej mieć

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

zrobić to. Nie wymaga to nawet otwierania nowych okien i podobnych rzeczy.

Kontroler MVC taki jak w Twoim przykładzie może wyglądać jak ten poniżej:

[HttpGet("[action]")]
public async Task<FileContentResult> DownloadFile()
{
    // ...
    return File(dataStream.ToArray(), "text/plain", "myblob.txt");
}

1
Masz rację, ale w takim razie jak możesz zarządzać błędami serwera w aplikacji jednostronicowej? W przypadku błędu normalnie usługa REST zwraca JSON z błędem, co powoduje, że aplikacja otwiera JSON w innym oknie przeglądarki, co nie jest tym, co chce zobaczyć użytkownik
Luca

2
Jeśli masz token dostępu, musisz go podać, nie działa
chris31389

To jest jasne i proste. Ale jeśli chcesz dokonać uwierzytelnienia, istnieje możliwość posiadania czegoś w rodzaju jednorazowego tokena. Więc zamiast mieć to w ten sposób, możesz mieć adres URL jako: example.com/myuri/report?tokenid=1234-1233 I zweryfikować identyfikator tokena w bazie danych. Oczywiście nie jest to prosty scenariusz i działa we wszystkich sytuacjach, ale może być rozwiązaniem w sytuacji, gdy masz dostęp do bazy danych przed zwróceniem raportu w postaci strumienia ..
GingerBeer

Uzyskaj adres URL pobierania z serwera. Serwer może więc przygotować adres URL z jednorazowym tokenem bezpieczeństwa.
Jens Alenius

8

Używam Angular 4 z obiektem 4.3 httpClient. Zmodyfikowałem odpowiedź, którą znalazłem na blogu technicznym Jsa, która tworzy obiekt linku, używa go do pobierania, a następnie niszczy.

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

} 

Wartość this.downloadUrl została wcześniej ustawiona tak, aby wskazywała na interfejs API. Używam tego do pobierania załączników, więc znam identyfikator, typ zawartości i nazwę pliku: używam interfejsu API MVC, aby zwrócić plik:

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

Klasa załącznika wygląda następująco:

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

Repozytorium filerep zwraca plik z bazy danych.

Mam nadzieję, że to komuś pomoże :)


7

Dla osób używających Redux Pattern

Dodałem do wygaszacza plików, jak @Hector Cuevas wymienił w swojej odpowiedzi. Używając Angular2 v. 2.3.1, nie musiałem dodawać @ types / file-saver.

Poniższy przykład to pobranie dziennika w formacie PDF.

Działania dziennika

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

Efekty dziennika

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

Usługa dziennika

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

Usługa HTTP

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

Reduktor dziennika Chociaż ustawia to tylko poprawne stany używane w naszej aplikacji, nadal chciałem go dodać, aby wyświetlić pełny wzór.

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: [] });
}

Mam nadzieję, że to jest pomocne.


7

Udostępniam rozwiązanie, które mi pomogło (każda poprawa jest bardzo mile widziana)

W Twojej usłudze „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'));
}

Część składowa:

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

W części składowej wywołujesz usługę bez subskrybowania odpowiedzi. Aby uzyskać pełną listę typów MIME OpenOffice, zapisz się na: http://www.openoffice.org/framework/documentation/mimetypes/mimetypes.html


7

Będzie lepiej, jeśli spróbujesz wywołać w sobie nową metodę subscribe

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

downloadFile(data)Funkcja wewnętrzna, którą musimy wykonaćblock, 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);
    }
}

5

Aby pobrać i wyświetlić pliki PDF , bardzo podobny fragment kodu wygląda jak poniżej:

  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!")
      );
  }

5

Oto coś, co zrobiłem w moim przypadku -

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

Odniesienie do rozwiązania znajduje się tutaj


4

Zaktualizuj do odpowiedzi Hectora przy użyciu funkcji oszczędzania plików i HttpClient dla kroku 2:

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

3

Otrzymałem rozwiązanie do pobierania z Angular 2 bez uszkodzenia, używając sprężynowego MVC i kątowego 2

Pierwszy - mój typ zwrotu to: - ResponseEntity z końca java. Tutaj wysyłam tablicę bajtów [] ma typ zwrotu ze sterownika.

2nd - aby dołączyć wygaszacz plików do swojego obszaru roboczego - na stronie indeksu jako:

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

3rd- w komponencie ts napisz ten kod:

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

                });

To da ci format pliku xls. Jeśli chcesz inne formaty, zmień typ nośnika i nazwę pliku z odpowiednim rozszerzeniem.


3

Miałem dzisiaj do czynienia z tym samym przypadkiem, musiałem pobrać plik pdf jako załącznik (plik nie powinien być renderowany w przeglądarce, ale zamiast tego pobierany). Aby to osiągnąć, odkryłem, że muszę pobrać plik w Angular Blobi jednocześnie dodać Content-Dispositionnagłówek w odpowiedzi.

To był najprostszy, jaki mogłem uzyskać (Angular 7):

Wewnątrz usługi:

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

Następnie, gdy chcę pobrać plik w komponencie, mogę po prostu:

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

AKTUALIZACJA:

Usunięto niepotrzebne ustawienie nagłówka z usługi


Jeśli używam window.location.href zamiast window.open, Chrome traktuje to jako pobieranie wielu plików.
DanO

to nie zadziała, jeśli potrzebujesz tokena
autoryzacji

3

Poniższy kod zadziałał dla mnie

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

2

W dotychczasowych odpowiedziach brakowało wglądu i ostrzeżeń. Możesz i powinieneś uważać na niezgodności z IE10 + (jeśli Ci zależy).

To jest kompletny przykład z częścią aplikacyjną i częścią serwisową po. Zwróć uwagę, że ustawiliśmy klauzulę observ: "response", aby złapać nagłówek nazwy pliku. Należy również pamiętać, że nagłówek Content-Disposition musi być ustawiony i ujawniony przez serwer, w przeciwnym razie bieżący HttpClient kątowy nie przekaże go dalej. Dodałem poniżej podstawowy fragment kodu dotnet .

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

Rdzeń Dotnet, z Content-Disposition i 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.");
        }
    }

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

1

Wystarczy umieścić urltak hrefjak poniżej.

<a href="my_url">Download File</a>

Czy to działa? Pojawia się błąd ... "ERROR TypeError:" Odmowa dostępu do 'file: ///Downloads/test.json' ze skryptu. ""
Jay

Dzięki, czy u pls możesz podzielić się wyglądem url? Czy to protokół pliku, http czy coś innego?
Jay

To protokół plików.
Harunur Rashid


1

Możesz również pobrać plik bezpośrednio z szablonu, w którym używasz atrybutu pobierania i [attr.href]możesz podać wartość właściwości z komponentu. To proste rozwiązanie powinno działać na większości przeglądarek.

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

Źródła: https://www.w3schools.com/tags/att_a_download.asp


1
Witamy w SO! Sprawdź, czy moje poprawki (skład i gramatyka) są pomocne.
B - rian

0

Jeśli wysyłasz parametry tylko na adres URL, możesz to zrobić w ten sposób:

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

w serwisie, który otrzymuje parametry


0

Ta odpowiedź sugeruje, że nie można pobierać plików bezpośrednio za pomocą AJAX, głównie ze względów bezpieczeństwa. Więc opiszę, co robię w tej sytuacji,

01. Dodaj hrefatrybut w tagu kotwicy wewnątrz component.htmlpliku,
np .: -

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

02. Wykonaj wszystkie poniższe kroki, component.tsaby ominąć poziom zabezpieczeń i wyświetlić okno dialogowe Zapisz jako wyskakujące okienko,
np .: -

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

Uwaga: ta odpowiedź zadziała, jeśli zostanie wyświetlony błąd „OK” z kodem stanu 200


0

Cóż, napisałem fragment kodu zainspirowany wieloma z powyższych odpowiedzi, który powinien z łatwością działać w większości scenariuszy, w których serwer wysyła plik z nagłówkiem dyspozycji treści, bez żadnych instalacji innych firm, z wyjątkiem rxjs i angular.

Po pierwsze, jak wywołać kod z pliku komponentu

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

Jak widać, jest to w zasadzie przeciętne wywołanie backendu z angular, z dwiema zmianami

  1. Obserwuję odpowiedź zamiast ciała
  2. Mówię wprost o odpowiedzi będącej kropką

Po pobraniu pliku z serwera w zasadzie deleguję całe zadanie zapisania pliku do funkcji pomocniczej, którą trzymam w osobnym pliku i importuję do dowolnego komponentu, którego potrzebuję

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

Nie ma już tajemniczych nazw plików GUID! Możemy użyć dowolnej nazwy dostarczonej przez serwer, bez konieczności jawnego określania jej w kliencie, lub nadpisać nazwę pliku dostarczoną przez serwer (jak w tym przykładzie). Można też łatwo, jeśli zajdzie taka potrzeba, zmienić algorytm wyodrębniania nazwy pliku z dyspozycji zawartości, aby odpowiadał jej potrzebom, a wszystko inne pozostanie nienaruszone - w przypadku błędu podczas takiej ekstrakcji po prostu przejdzie 'null' jako nazwa pliku.

Jak już wskazała inna odpowiedź, IE wymaga specjalnego traktowania, jak zawsze. Ale ponieważ chromium Edge pojawi się za kilka miesięcy, nie martwiłbym się tym podczas tworzenia nowych aplikacji (miejmy nadzieję). Jest też kwestia unieważnienia adresu URL, ale nie jestem tego pewien, więc jeśli ktoś mógłby pomóc w komentarzach, byłoby wspaniale.


0

Jeśli karta otwiera się i zamyka bez pobierania czegokolwiek, próbowałem podążać za fałszywym łączem kotwicy i zadziałało.

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);  }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.