我有一个WebApi / MVC应用程序,我正在为它开发一个angular2客户端(以取代MVC)。我在理解Angular如何保存文件时遇到了一些麻烦。

请求是可以的(与MVC一起工作很好,我们可以记录接收到的数据),但我不知道如何保存下载的数据(我主要遵循与本文相同的逻辑)。我确信这是愚蠢的简单,但到目前为止,我根本没有领会它。

组件函数的代码如下。我尝试了不同的替代方案,blob方式应该是我所理解的方式,但URL中没有createObjectURL函数。我甚至不能在窗口中找到URL的定义,但显然它存在。如果我使用FileSaver.js模块,我得到同样的错误。所以我猜这是最近改变的或者还没有实现的东西。我如何触发文件保存在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);
}

为了完整起见,获取数据的服务如下所示,但它所做的唯一一件事是发出请求并在没有映射的情况下传递数据:

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

当前回答

对于那些使用Redux模式

我在文件保存器中添加了@Hector Cuevas的名字,在他的回答中。使用Angular2 v. 2.3.1,我不需要添加@types/file-saver。

以PDF格式下载日志为例。

日记账操作

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

期刊效应

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

期刊服务

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

HTTP服务

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

轴颈减速器 虽然这只设置了我们应用程序中使用的正确状态,但我仍然想添加它来显示完整的模式。

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

我希望这对你有帮助。

其他回答

我今天也遇到了同样的情况,我不得不下载一个pdf文件作为附件(该文件不应该在浏览器中呈现,而是下载)。为了达到这个目的,我发现我必须在Angular Blob中获取文件,同时在响应中添加一个Content-Disposition头。

这是我能得到的最简单的(Angular 7):

服务内部:

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

然后,当我需要下载组件中的文件时,我可以简单地:

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

更新:

从服务中删除不必要的报头设置

好吧,我写了一段代码,灵感来自于上面的许多答案,应该很容易在大多数情况下工作,即服务器发送一个带有内容处置头的文件,而不需要任何第三方安装,除了rxjs和angular。

首先,如何从组件文件调用代码

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

正如你所看到的,它基本上是angular的平均后端调用,只有两个变化

我观察的是反应而不是身体 我明确表示响应是一个blob

一旦从服务器获取文件,原则上我就将保存文件的整个任务委托给helper函数(我将其保存在一个单独的文件中),并将其导入到需要的组件中

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

这样就不再有神秘的GUID文件名了!我们可以使用服务器提供的任何名称,而不必在客户端显式指定它,或者覆盖服务器提供的文件名(如本例所示)。 此外,如果需要的话,可以很容易地改变从内容配置中提取文件名的算法来满足他们的需求,其他一切都不会受到影响——如果在提取过程中出现错误,它只会传递'null'作为文件名。

正如已经指出的另一个答案,IE需要一些特殊处理,一如既往。但随着几个月后chromium edge的推出,我在开发新应用时就不会担心这个问题了(希望如此)。 还有撤销URL的问题,但我有点不太确定,所以如果有人能在评论中帮忙,那就太棒了。

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

到目前为止,我发现这些答案既缺乏洞察力,也缺乏警告。你可以也应该注意与IE10+的不兼容性(如果你在乎的话)。

这是完整的示例,其中包括应用程序部分和服务部分。注意,我们设置了observe: "response"来捕捉文件名的头信息。还要注意,Content-Disposition报头必须由服务器设置并公开,否则当前的Angular HttpClient将不会传递它。我在下面添加了一段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;
}

Dotnet核心,具有内容处理和媒体类型

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

我找到的答案要么是在Angular 13.1上无法运行,要么是不必要的复杂(就像被接受的例子一样),而且没有解释为什么这是必要的。 对于像Angular这样不断变化的生态系统来说,要求附加版本号是非常有用的。

用户@Aleksandar Angelov提供的迷你代码片段绕过了会话系统,因此需要进行不必要的授权。

根据他的回答,我得出了以下代码:

  downloadConfiguration(url: string, filename: string) {
    this.http.get(url, {responseType: 'blob'})
    .subscribe(data => {
      // console.log("data", data);
      var url = window.URL.createObjectURL(data);
      let downloadLink = document.createElement('a');
      downloadLink.href = url
      downloadLink.setAttribute('download', filename);
      downloadLink.click();
    });
  }