Comment télécharger un fichier avec Angular2 ou supérieur

182

J'ai une application WebApi / MVC pour laquelle je développe un client angular2 (pour remplacer MVC). J'ai du mal à comprendre comment Angular enregistre un fichier.

La demande est correcte (fonctionne bien avec MVC, et nous pouvons enregistrer les données reçues) mais je ne peux pas comprendre comment enregistrer les données téléchargées (je suis principalement la même logique que dans cet article ). Je suis sûr que c'est stupidement simple, mais jusqu'à présent, je ne le saisis tout simplement pas.

Le code de la fonction du composant est ci-dessous. J'ai essayé différentes alternatives, la méthode blob devrait être la manière d'aller aussi loin que je l'ai compris, mais il n'y a pas de fonction createObjectURLdans URL. Je ne trouve même pas la définition de URLdans window, mais apparemment, elle existe. Si j'utilise le FileSaver.jsmodule, j'obtiens la même erreur. Je suppose donc que c'est quelque chose qui a changé récemment ou qui n'est pas encore mis en œuvre. Comment puis-je déclencher l'enregistrement du fichier dans 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);
}

Par souci d'exhaustivité, le service qui récupère les données est ci-dessous, mais la seule chose qu'il fait est d'émettre la requête et de transmettre les données sans mappage si elle réussit:

downloadfile(runname: string, type: string){
   return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
            .catch(this.logAndPassOn);
}
rll
la source
Vous ne pouvez pas télécharger de gros fichiers avec cette méthode. Vous atteindrez la limite de mémoire par onglet. Cela peut être aussi bas que 1 à 2 Go.
Matthew B.
@MatthewB. j'aurais aimé dire ce qui était mieux.
steve
Pour les téléchargements de fichiers volumineux, vous devez spécifier un nouvel onglet. Par exemple, si vous simulez un clic <A>, la cible doit être égale à "_blank" ou envoyez un formulaire. Je ne pense pas qu'il existe un moyen propre de contourner la limitation de la taille des fichiers volumineux avec les requêtes de style Ajax.
Matthew B.

Réponses:

181

Le problème est que l'observable s'exécute dans un autre contexte, donc lorsque vous essayez de créer l'URL var, vous avez un objet vide et non l'objet blob souhaité.

L'une des nombreuses façons de résoudre ce problème est la suivante:

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

Lorsque la demande est prête, elle appellera la fonction "downloadFile" qui est définie comme suit:

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

le blob a été créé parfaitement et donc l'URL var, si n'ouvre pas la nouvelle fenêtre, veuillez vérifier que vous avez déjà importé 'rxjs / Rx';

  import 'rxjs/Rx' ;

J'espère que cela pourra vous aider.

Alejandro Corredor
la source
9
Qu'est- this._reportService.getReport()ce que c'est et que renvoie-t-il
Burjua
3
@Burjua le getReport()retourne unthis.http.get(PriceConf.download.url)
ji-ruh
6
Le problème que j'ai est que la fenêtre s'ouvre et se ferme immédiatement sans télécharger le fichier
Braden Brown
7
Comment pouvons-nous définir le nom de fichier ici? par défaut, il choisit une valeur numérique comme nom
Saurabh
8
J'ai utilisé le code ci-dessus pour télécharger un fichier à partir de la réponse de l'API, mais j'obtiens une erreur lors de la création de la partie Blob "La réponse de type n'est pas assignable au type Blobpart". Veuillez aider si quelqu'un connaît ce problème
knbibin
92

Essayez ceci !

1 - Installer les dépendances pour afficher la fenêtre de sauvegarde / ouvrir le fichier

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

2- Créez un service avec cette fonction pour recevoir les données

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- Dans le composant analyser le blob avec 'file-saver'

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

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

Cela fonctionne pour moi!

Hector Cuevas
la source
1
J'ai utilisé l'étape 2 en combinaison avec la réponse de @Alejandro et cela a fonctionné sans avoir besoin d'installer un économiseur de fichiers ...
Ewert
5
Je vous remercie! Cela fonctionne parfaitement! Je me demande si nous pouvons obtenir le nom de fichier défini sur l'en-tête de la réponse. Est-ce possible?
jfajunior
2
erreur Av5 L'argument de type 'RequestOptions' n'est pas assignable au paramètre de type '{headers ?: HttpHeaders | {[en-tête: chaîne]: chaîne | chaîne[]; };
giveJob
Celui-ci n'est cependant pas adapté au téléchargement de gros fichiers.
Reven le
61

Si vous n'avez pas besoin d'ajouter des en-têtes dans la demande, pour télécharger un fichier dans Angular2, vous pouvez faire un simple :

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

dans votre composant.

surfealokesea
la source
4
Quelqu'un peut-il s'il vous plaît dire pourquoi cette réponse est rejetée? Le sujet est de télécharger un fichier en utilisant angular2. Si cette méthode fonctionne pour effectuer un téléchargement simple, elle doit également être marquée comme une réponse valide.
Saurabh Shetty
5
@SaurabhShetty, cela n'aidera pas au cas où vous voudriez envoyer des en-têtes personnalisés, que faire si vous voulez envoyer un jeton d'authentification par exemple? Si vous regardez dans la question OP, vous pouvez voir qu'il utilise authHttp!
A.Akram
6
Je comprends les votes négatifs, mais cette réponse a résolu mon problème.
JoeriShoeby le
1
Si vous laissez le serveur renvoyer l'url dans un certain contexte, le serveur pourrait préparer l'url. ex: objet: MyRecord.Cover. La couverture peut être une URL vers une image sur le serveur. Lorsque vous appelez get (Myrecord), vous laissez le serveur renvoyer l'url préparée (Cover), avec le jeton de sécurité et d'autres en-têtes définis.
Jens Alenius
2
C'est une réponse qui fonctionne. Juste parce qu'il n'a pas <insérer une fonction utile ici> qui ne fait pas de réponse.
gburton
47

Ceci est pour les gens qui cherchent comment le faire en utilisant HttpClient et File-Saver:

  1. Installer l'économiseur de fichiers

npm installer économiseur de fichiers --save

npm install @ types / économiseur de fichiers --save

Classe de service API:

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

Composant:

import { saveAs } from 'file-saver';
exportPdf() {
    this.api_service.export().subscribe(data => saveAs(data, `pdf report.pdf`));
}
Justin
la source
1
Comment afficher la taille du fichier dans le navigateur au démarrage du téléchargement? J'envoie la taille du fichier comme content-length dans l'en-tête http.
humbleCoder
35

Que dis-tu de ça?

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

Je pourrais faire avec.
pas besoin de paquet supplémentaire.

harufumi.abe
la source
3
Si simple mais c'est celui qui fonctionne parfaitement. Cela n'encombre pas le DOM, ne crée aucun élément. J'ai combiné cette solution avec certains des éléments ci-dessus et cela fonctionne comme un charme.
Chax
20

Comme mentionné par Alejandro Corredor il s'agit d'une simple erreur de portée. Le subscribeest exécuté de manière asynchrone et opendoit être placé dans ce contexte, de sorte que les données aient fini de se charger lorsque nous déclenchons le téléchargement.

Cela dit, il existe deux façons de procéder. Comme la documentation le recommande, le service se charge d'obtenir et de mapper les données:

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

Ensuite, sur le composant, nous nous abonnons simplement et traitons les données mappées. Il y a deux possibilités. La première , comme suggéré dans l'article original, mais nécessite une petite correction comme l'a noté 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 deuxième façon serait d'utiliser FileReader. La logique est la même mais nous pouvons explicitement attendre que FileReader charge les données, évitant l'imbrication et résolvant le problème asynchrone.

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

Remarque: j'essaie de télécharger un fichier Excel, et même si le téléchargement est déclenché (cela répond donc à la question), le fichier est corrompu. Voir la réponse à ce message pour éviter le fichier corrompu.

rll
la source
7
Je pense que la raison pour laquelle le fichier est corrompu est que vous chargez resdans le blob et que vous le souhaitez réellement res._body. Cependant, _bodyc'est une variable privée et non accessible. À ce jour .blob()et .arrayBuffer()sur un objet de réponse http, ils n'ont pas été implémentés dans Angular 2. text()et json()sont les deux seules options, mais les deux vont brouiller votre corps. Avez-vous trouvé une solution?
sschueller
1
salut @rll, j'ai suivi les étapes ci-dessus et je m'inscris comme terminé. Je ne pouvais toujours pas voir le fichier téléchargé. Je n'ai pas non plus vu d'erreur. S'il vous plaît aider
AishApp
1
Les 2 options me permettent de télécharger le fichier, mais il charge d'abord les données en arrière-plan. Que faire si j'ai un gros fichier à télécharger?
f123
1
Ma solution consiste simplement <a href=""></a>à télécharger un fichier.
user2061057
1
Je sais que c'est une vieille réponse, mais elle figure en bonne place dans les résultats de recherche et c'est la réponse acceptée: la ligne `headers.append ('responseType', 'arraybuffer');` est erronée. C'est une option, pas un en-tête. Regle-le, s'il te plait. Aaaand ... Les en-têtes sont créés et non utilisés. Inutile.
Stevo
17

Téléchargez la solution * .zip pour angular 2.4.x: vous devez importer ResponseContentType depuis '@ angular / http' et changer responseType en ResponseContentType.ArrayBuffer (par défaut, 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
la source
16

Pour les nouvelles versions angulaires:

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
la source
Merci, fonctionne avec Angular 8. Je ne sais pas pourquoi c'était si difficile à trouver.
MDave
11

Le téléchargement de fichiers via ajax est toujours un processus pénible et à mon avis, il est préférable de laisser le serveur et le navigateur faire ce travail de négociation de type de contenu.

Je pense que c'est mieux d'avoir

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

pour le faire. Cela ne nécessite même pas l'ouverture de nouvelles fenêtres et des trucs comme ça.

Le contrôleur MVC comme dans votre exemple peut être comme celui ci-dessous:

[HttpGet("[action]")]
public async Task<FileContentResult> DownloadFile()
{
    // ...
    return File(dataStream.ToArray(), "text/plain", "myblob.txt");
}
Bière de gingembre
la source
1
Vous avez raison, mais comment pouvez-vous gérer les erreurs de serveur dans l'application monopage? En cas d'erreur, normalement, un service REST renvoie le JSON avec l'erreur, entraînant ainsi l'application d'ouvrir le JSON dans une autre fenêtre de navigateur, ce qui n'est pas ce que l'utilisateur veut voir
Luca
2
Si vous avez un jeton d'accès, vous devez fournir cela ne fonctionne pas
chris31389
C'est clair et simple. Mais si vous voulez faire une authentification, il y a une possibilité d'avoir quelque chose comme un jeton unique. Donc, au lieu de l'avoir comme ça, vous pouvez avoir l'url comme: example.com/myuri/report?tokenid=1234-1233 Et vérifier l'ID du jeton dans la base de données. Bien sûr, ce n'est pas un scénario simple et fonctionne dans toutes les situations, mais peut être une solution dans une situation où vous avez accès à la base de données avant de renvoyer le rapport sous forme de flux ..
GingerBeer
Obtenez l'URL de téléchargement du serveur. Ainsi, le serveur peut préparer l'URL avec un jeton de sécurité unique.
Jens Alenius
8

J'utilise Angular 4 avec l'objet httpClient 4.3. J'ai modifié une réponse trouvée dans le blog technique de Js qui crée un objet lien, l'utilise pour faire le téléchargement, puis le détruit.

Client:

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

} 

La valeur de this.downloadUrl a été définie précédemment pour pointer vers l'API. J'utilise ceci pour télécharger des pièces jointes, donc je connais l'id, le contentType et le nom de fichier: j'utilise une api MVC pour renvoyer le fichier:

 [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 classe de pièce jointe ressemble à ceci:

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

Le référentiel filerep renvoie le fichier de la base de données.

J'espère que cela aide quelqu'un :)

Davaus
la source
7

Pour ceux qui utilisent Redux Pattern

J'ai ajouté dans l'économiseur de fichiers comme @Hector Cuevas nommé dans sa réponse. En utilisant Angular2 v. 2.3.1, je n'ai pas eu besoin d'ajouter le @ types / file-saver.

L'exemple suivant consiste à télécharger un journal au format PDF.

Les actions du journal

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

Les effets du journal

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

Le service journal

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

Le service HTTP

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

Le réducteur de journal Bien que cela ne définisse que les états corrects utilisés dans notre application, je voulais toujours l'ajouter pour afficher le modèle complet.

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

J'espère que ceci est utile.

Casper Nybroe
la source
7

Je partage la solution qui m'a aidé (toute amélioration est grandement appréciée)

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

Partie composant :

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

Sur le composant, vous appelez le service sans souscrire à une réponse. L'abonnement pour une liste complète des types mime openOffice voir: http://www.openoffice.org/framework/documentation/mimetypes/mimetypes.html

Ismail H
la source
7

Ce sera mieux si vous essayez d'appeler la nouvelle méthode en vous subscribe

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

downloadFile(data)Fonction intérieure que nous devons faireblock, 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
la source
5

Pour télécharger et afficher des fichiers PDF , un code très similaire extrait est comme ci-dessous:

  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
la source
5

Voici quelque chose que j'ai fait dans mon cas -

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

La solution est référencée à partir d' ici

Tushar Walzade
la source
4

Mettez à jour la réponse d'Hector à l'aide de l'économiseur de fichiers et de HttpClient pour l'étape 2:

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

J'ai une solution pour télécharger à partir d'angular 2 sans être corrompu, en utilisant spring mvc et angular 2

1er- mon type de retour est: - ResponseEntity de la fin de Java. Ici, j'envoie le tableau d'octets [] a un type de retour du contrôleur.

2ème - pour inclure l'économiseur de fichiers dans votre espace de travail - dans la page d'index comme:

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

3ème au composant ts écrivez ce code:

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

                });

Cela vous donnera le format de fichier xls. Si vous voulez d'autres formats, changez le type de média et le nom du fichier avec l'extension droite.

user2025069
la source
3

J'étais confronté à ce même cas aujourd'hui, j'ai dû télécharger un fichier pdf en pièce jointe (le fichier ne devait pas être rendu dans le navigateur, mais téléchargé à la place). Pour y parvenir, j'ai découvert que je devais obtenir le fichier dans un format angulaire Blobet, en même temps, ajouter unContent-Disposition tête dans la réponse.

C'était le plus simple que je puisse obtenir (Angular 7):

À l'intérieur du service:

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

Ensuite, lorsque j'ai besoin de télécharger le fichier dans un composant, je peux simplement:

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

METTRE À JOUR:

Suppression du paramètre d'en-tête inutile du service

Gabriel Sezefredo
la source
Si j'utilise window.location.href au lieu de window.open, Chrome le traite comme plusieurs téléchargements de fichiers.
DanO
cela ne fonctionnera pas si vous avez besoin d'un jeton d'authentification dans l'en-tête
garg10may
3

Le code suivant a fonctionné pour moi

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();
Basilic
la source
2

Jusqu'à présent, j'ai trouvé que les réponses manquaient de perspicacité et d'avertissement. Vous pouvez et devez surveiller les incompatibilités avec IE10 + (si vous vous en souciez).

Ceci est l'exemple complet avec la partie application et la partie service après. Notez que nous définissons observer: "response" pour attraper l'en-tête du nom de fichier. Notez également que l'en-tête Content-Disposition doit être défini et exposé par le serveur, sinon le Angular HttpClient actuel ne le transmettra pas. J'ai ajouté un morceau de code de base dotnet pour cela ci-dessous.

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, avec Content-Disposition et 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
la source
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
la source
1

Il suffit de mettre le urlcomme hrefci - dessous.

<a href="my_url">Download File</a>
Harunur Rashid
la source
Est-ce que ça marche? J'obtiens une erreur ... "ERROR TypeError:" Accès à 'file: ///Downloads/test.json' à partir du script refusé. ""
Jay
Merci, pouvez-vous partager à quoi ressemble l'URL? Identifier le protocole de fichier ou http ou autre chose?
Jay
C'est le protocole de fichier.
Harunur Rashid
1

Vous pouvez également télécharger un fichier directement à partir de votre modèle où vous utilisez l'attribut de téléchargement et [attr.href]vous pouvez fournir une valeur de propriété à partir du composant. Cette solution simple devrait fonctionner sur la plupart des navigateurs.

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

Référence: https://www.w3schools.com/tags/att_a_download.asp

Max
la source
1
Bienvenue à SO! Veuillez vérifier si mes corrections (de composition et de grammaire) sont utiles.
B - rian
0

Si vous envoyez uniquement les paramètres à une URL, vous pouvez le faire de cette façon:

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

dans le service qui reçoit les paramètres

Joël Sulca Cordova
la source
0

Cette réponse suggère que vous ne pouvez pas télécharger des fichiers directement avec AJAX, principalement pour des raisons de sécurité. Je vais donc décrire ce que je fais dans cette situation,

01. Ajoutez un hrefattribut dans votre balise d'ancrage à l'intérieur du component.htmlfichier,
par exemple: -

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

02. Faites toutes les étapes suivantes dans votre component.tspour contourner le niveau de sécurité et afficher la boîte de dialogue Enregistrer en tant que popup,
par exemple: -

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

Remarque: cette réponse fonctionnera si vous obtenez une erreur "OK" avec le code d'état 200

Indrajith Ekanayake
la source
0

Eh bien, j'ai écrit un morceau de code inspiré de la plupart des réponses ci-dessus qui devrait facilement fonctionner dans la plupart des scénarios où le serveur envoie un fichier avec un en-tête de disposition de contenu, sans aucune installation tierce, sauf rxjs et angular.

Tout d'abord, comment appeler le code à partir de votre fichier de composant

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

Comme vous pouvez le voir, il s'agit essentiellement de l'appel backend moyen d'angular, avec deux changements

  1. J'observe la réponse au lieu du corps
  2. Je suis explicite sur le fait que la réponse est un blob

Une fois le fichier récupéré sur le serveur, je délègue en principe toute la tâche de sauvegarde du fichier à la fonction d'assistance, que je garde dans un fichier séparé, et que je l'importe dans le composant dont j'ai besoin

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

Là, plus de noms de fichiers GUID cryptiques! Nous pouvons utiliser n'importe quel nom fourni par le serveur, sans avoir à le spécifier explicitement dans le client, ou écraser le nom de fichier fourni par le serveur (comme dans cet exemple). De plus, on peut facilement, si nécessaire, changer l'algorithme d'extraction du nom de fichier de la disposition de contenu pour répondre à leurs besoins, et tout le reste restera inchangé - en cas d'erreur lors d'une telle extraction, il passera simplement `` null '' comme nom de fichier.

Comme une autre réponse l'a déjà souligné, IE a besoin d'un traitement spécial, comme toujours. Mais avec Chrome Edge à venir dans quelques mois, je ne m'inquiéterais pas à ce sujet lors de la création de nouvelles applications (espérons-le). Il y a aussi la question de la révocation de l'URL, mais je ne suis pas si sûr de cela, donc si quelqu'un pouvait aider avec cela dans les commentaires, ce serait génial.

Dibyodyuti Mondal
la source
0

Si un onglet s'ouvre et se ferme sans rien télécharger, j'ai essayé de suivre avec un lien d'ancrage simulé et cela a fonctionné.

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
la source