Search code examples
angularspringangular7angular8

Export file with custom name


I want to implement this code example which is used to export data from Spring BE.

@GetMapping("export/{ids}")
    public void export(HttpServletResponse response, @PathVariable List<Integer> ids) {

        List<Transactions> transactions = (List<Transactions>) transactionService.findAll(ids);
        List<TransactionExcelEntry> entries = transactions.stream().map(payment_transaction_mapper::toExcel).collect(Collectors.toList());

        List<String> headers = Arrays.asList("Id", "Name", "Type", "Created at");
        try {
            response.addHeader("Content-disposition", "attachment; filename=Transactions.xlsx");
            response.setContentType("application/vnd.ms-excel");
            new SimpleExporter().gridExport(headers, entries, "id, name", response.getOutputStream());
            response.flushBuffer();

        } catch (IOException ex) {
            LOG.debug("parsing of transactions failed");
        }
    }

Export button:

<button class="dropdown-item" (click)="export()">XLS</button>

Export functionality:

export() {
    var newPagination = new Pagination();
    newPagination.size = this.pagination.size * this.pagination.total
    this.transactionService.search(newPagination, this.formGroup.value)
        .subscribe(result => {
            this.formGroup.enable();
            const query = result.content.map(t => t.id).join(',');
            this.transactionService.exportRows(query).subscribe(data => {
                const a = document.createElement('a');

                a.href = window.URL.createObjectURL(data);
                a.download = 'export.xls';
                a.click();
            });
        }, (error) => {
            this.formGroup.enable();
        });
  }

exportRows(query) {
    return this.http.get(`/api/transactions/export`, { responseType: 'blob' });
}

I want to generate the name of the file into the Java BE and download it from the Angular FE. How this functionality can be implemented?


Solution

  • You can retrieve the file name of your blob by accessing the response headers and retrieving Content-Disposition.

    To do that, change a little your HttpClient.get call, by providing additional option observe: 'response'.

    To make it clear, we create a dedicated ExportResponse to expose only needed data to our component/or other service method :

    export type ExportResponse = {
      blob: Blob,
      fileName: string
    }
    

    The exportRows retrieves Response Headers :

    exportRows(query): ExportResponse {
      return this.http.get(`/api/transactions/export`, { 
        observe: 'response',
        responseType: 'blob' 
      }.pipe(
        map(response => {
          const contentDisposition = response.headers.get('content-disposition');
          return {
            blob: response.body,
            fileName: getContentDispositionFileName(contentDisposition)
          }
        })
      );
    }
    

    The getContentDispositionFileName method is in charge to extract the filename from the header received:

    getContentDispositionFileName(contentDisposition: string) {
      let filename = 'default-file-name';
    
      var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
      var matches = filenameRegex.exec(contentDisposition);
      if (matches != null && matches[1]) {
        filename = matches[1].replace(/['"]/g, '');
      }
    
      return filename;
    }
    

    This RegExp pattern comes from Winter Soldier's answer, and extracts the filename's part.

    Then you can use ExportResponse in your initial method:

    this.transactionService.exportRows(query).subscribe(response => {
      const a = document.createElement('a');
      a.href = window.URL.createObjectURL(response.blob);
      a.download = response.fileName;
      a.click();
    });
    

    Important note

    If you have CORS enabled, be careful to allow your backend to send and authorize your frond-end to access to Content-Disposition header.

    To do that, add Access-Control-Expose-Headers: Content-Disposition in your response.

    response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);