Ahmed Elkoussy
Ahmed Elkoussy

Reputation: 8568

Calling Spring boot api that downloads a pdf from Angular 6 is giving error can't parse response

I have a spring boot API that creates a pdf report and download it, if I call the API directly in the browser, the pdf is created and downloaded directly, but when I call that GET API from Angular 6 I get the following error:

The Spring boot (java) code:

@RequestMapping(value = "/app_report/en", method = RequestMethod.GET)
@ResponseBody
public void getEnRpt(HttpServletResponse response, @RequestParam("appNum") String appNum) throws JRException, IOException, SQLException {

    JasperReport jasperReport = JasperCompileManager.compileReport("./src/main/resources/jasperReports/App_created_en.jrxml");

    Connection connection = dataSource.getConnection();

    Map<String, Object> params = new HashMap<>();
    params.put("P_APP_NO", appNum);
    JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, params, connection);

    response.setContentType("application/x-pdf");
    response.setHeader("Content-disposition", "inline; filename=App_report_en.pdf");

    final OutputStream outStream = response.getOutputStream();
    JasperExportManager.exportReportToPdfStream(jasperPrint, outStream);
}

The Angular code that calls the API:( I tried multiple ways, two are shown here):

this.http.get(this.api_url + reportUrl, {
  responseType: 'blob'
}, ).subscribe((response: File) => {
  console.log('report is downloaded');
});

this.http.get(this.api_url + reportUrl).subscribe(
  (response) => {
    console.log('report is downloaded');
  });

The console error that I get after the Angular calls the API:

error:
SyntaxError: Unexpected token % in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttpRequest.onLoad 
message: "Http failure during parsing for https://localhost:7001/reports/app_report/en?appNum=xxxxxxx"
​
name: "HttpErrorResponse"
​
ok: false
​
status: 200
​
statusText: "OK"
​
url: "https://localhost:7001/reports/app_report/en?appNum=xxxxxx"

I only need to call the API like the call that happens in the browser so that the pdf is downloaded right away

The response headers are as follows:

Content-disposition 
inline; filename=App_report_en.pdf
Content-Type    
application/x-pdf

Why Angular is not downloading the file like what happens in the browser if I call that API? (especially since the request is successful)

Upvotes: 2

Views: 4452

Answers (2)

Newaz Sharif Amit
Newaz Sharif Amit

Reputation: 197

Spring Boot + Jaspersoft + Angular

In component.ts

reportGenerate() {
this.reportService.generateReport(this.form.value).subscribe(response => {
  let url = window.URL.createObjectURL(response.data);
  let a = document.createElement('a');
  document.body.appendChild(a);
  a.setAttribute('style', 'display: none');
  a.setAttribute('target', 'blank');
  a.href = url;
  a.download = response.filename;
  a.click();
  window.URL.revokeObjectURL(url);
  a.remove();
}, error => {
  console.log(error);
});

}

In service.ts

   public generateReport(data: Data) {
   
    return this.httpService.getReport(url, data)
      .pipe(map((response) => {
        return {
          filename: 'report.pdf',
          data: new Blob([response], { type: 'application/pdf' })
        };
      }));
  }

call rest api

    getReport(url: any, data: any): Observable<any> {
        const headers = new HttpHeaders({
            'Authorization': 'Bearer ' + this.tokenStoreService.getToken()
        })
        return this.http.post(this.URL + url, data, { headers, responseType: 'arraybuffer' as 'json'});
 }

Spring Boot

public JasperReport getJasperReport(String reportJrxml) throws Exception {
     Resource resource = new ClassPathResource(reportJrxml);
     InputStream inputStream = resource.getInputStream();
     JasperDesign jasperDesign = JRXmlLoader.load(inputStream);

    return JasperCompileManager.compileReport(jasperDesign);
}

Upvotes: 0

David
David

Reputation: 34455

To avoid the JSON parsing issue, you need to tell the http client that the response will be a blob, using responseType: 'blob' in requests options.

Then, to actually have the navigator open the file, you can follow the instructions from this blog

    const fileName = "report.pdf";
    this.http.get(this.api_url + reportUrl, {  responseType: 'blob'})
    .subscribe((blob: Blob) => {
    console.log('report is downloaded');

    if (navigator.msSaveBlob) 
    { 
        // IE 10+
        navigator.msSaveBlob(blob, filename);
    }
    else 
    {
        let link = document.createElement("a");
        if (link.download !== undefined) 
        {
            let 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);
        }
        else
        {
            //html5 download not supported
        }
    }   
});

This should work for IE 10+ and other modern browsers, apart from iOs (see CanIuse)

Upvotes: 8

Related Questions