2

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?

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808

2 Answers2

2

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);
Thierry Falvo
  • 5,892
  • 2
  • 21
  • 39
0

If you want the file name to be generated automatically you can try the below approach

export(filename) {
  this.transactionService.search(newPagination, this.formGroup.value)
        .subscribe(result => {
            this.formGroup.enable();
            const timeStamp: number = new Date().getTime();
            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 = `${fileName}-${timeStamp}.xls`;
                a.click();
            });
        }, (error) => {
            this.formGroup.enable();
        });
}

On the button click you can send the file name or from the service call you canget the filename from server

<button class="dropdown-item" (click)="export('Transaction')">XLS</button>
Nidhin Kumar
  • 3,278
  • 9
  • 40
  • 72