Encountering an issue when trying to download a PDF from an Angular 6 frontend using a Spring Boot API - receiving an error related to

When I directly call the Spring Boot API in the browser, it successfully creates and downloads a PDF report. However, when I try to make the same GET request from Angular 6, I encounter the following error:

Here is the code snippet for the Spring Boot (Java) API:

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

Below are two versions of the Angular code attempting to call the API:

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

This is the console error that appears after making the API calls from Angular:

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"

The goal is to replicate the direct download behavior seen in the browser when calling the API from Angular.

The response headers provided by the API are:

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

Why does Angular not download the file as expected, even though the request is successful?

Answer №1

To solve the issue with JSON parsing, it is important to inform the http client that the response will be in blob format by including responseType: 'blob' in the request options.

Next, to enable the browser to open the file, you can refer to the steps outlined in this blog post


    const fileName = "report.pdf";
    this.http.get(this.api_url + reportUrl, {  responseType: 'blob'})
    .subscribe((blob: Blob) => {
    console.log('report has been downloaded');
    
    if (navigator.msSaveBlob) 
    { 
        // For 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 method should function correctly on Internet Explorer 10 and newer versions of browsers, with the exception of iOS devices (refer to CanIuse)

Answer №2

Integrating Spring Boot, Jaspersoft, and Angular

In the component.ts file

generateReport() {
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 the service.ts file

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

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

Setting up Jasper Report in 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);
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for refreshing the direction route in google-maps-react

I have an array of coordinates, and when I add a new coordinate to the array, I want the route direction on the map to update accordingly. This is the code snippet from my googlemap.js file: /* global google */ import React, { Component } from "react ...

Initiate the function once the condition is satisfied (contains the class "in-view")

Here is the code for an animation: var setInter = null; function startAnimation() { var frames = document.getElementById("animation").children; var frameCount = frames.length; var i = 0; setInter = setInterval(function () { fr ...

Tips for creating a curved shape using fabric.js

I am encountering an issue while trying to draw an arc using a circle with a start and end angle in my code. Here is the snippet I am working with: var circle = new fabric.Circle({ radius: 30, left: 20, top: 20, fill: " ...

What are the steps to connect to multiple databases with ExpressJS?

I have a server with 3 databases containing identical tables. The databases are named DB1, DB2, and DB3. When working with a specific database, I utilize the following code in app.js: var cnxDB = require('./routes/cnxDB'); app.post('/user ...

Manipulating the information pulled from an external PHP script that is currently being displayed

Although I don't consider myself a professional, I am determined to learn some web languages this summer to be able to turn my design ideas into prototypes. Currently, I am struggling to figure out how to manipulate elements that are being echoed bac ...

What is the best way to send the input text to the filter component in my React application?

I am currently working on developing an application utilizing the "Rick and Morty API" to display a list of characters with various attributes such as gender, alive status, images, etc. My main goal is to implement a search bar that allows users to search ...

Troubleshooting Nested jQuery Plugin Selector Problems

I'm looking to have the ability to nest one plugin inside another. However, my selectors seem too broad and are capturing elements within the nested plugin as well. For instance, consider the following HTML structure: <div class="my-plugin"> ...

"Unraveling the depths of a multidimensional array in JavaScript: a comprehensive guide

Seeking assistance from this wonderfully helpful community :) It seems like I might be declaring and creating my arrays incorrectly, as I am trying to add content to an array of arrays but unable to retrieve anything from it. Here's the code snippet ...

Showing text instantly upon clicking a radio button, in real-time

I'm working with a set of radio buttons that are linked to numbers ranging from 1 to 24. I need to show the selected number in another part of the page when a radio button is clicked. What would be the best way to achieve this? ...

Expanding on Angular's virtual scroll feature: automatically add new items as you reach the bottom of the

I'm facing a challenge in my Angular application where I want to implement virtual scroll. The items displayed on the list are the outcome of a remote paged search. My goal is to fetch more results (trigger the next page) every time I scroll down to t ...

Tables that respond to changes in screen size, allowing nested table cells to inherit widths

I am currently working on a responsive table with unique content in each row and a concertina feature for expanding rows. When the concertina is activated, it adds another row to the table below the current row, using a td element with a colspan attribute ...

Is it possible to integrate TypeScript 5.0 decorators into React components?

Every time I add decorators to my class, they always get called with the arguments specified for legacy decorators: a target, property key, and property descriptor. I am interested in using TypeScript 5.0 decorators. Is this feasible, and if so, how can I ...

Guide on invoking a POST endpoint within another POST API in a Node.js Express server

I encountered an issue while attempting to use fetch in react.js with a backend node.js API URL, which then proceeds to make a POST API call within the server to another route utilizing a different URL. How can I tackle this task effectively? See the code ...

Looping the jQuery Ajax success function

Utilizing Ajax to input an array of data into a database. At the moment, when clicking on "#bookingbutton," it triggers a return block of HTML containing the ".select-room-button" button. I have incorporated the code for ".select-room-button" within the ...

Having trouble with Window.open on Mobile Devices and Canvas?

I've created a unique "button" within the div element. This button is designed to detect user interaction, such as clicks or taps, and when triggered, it should open a new window with the URL: "http://www.google.com". However, while this functionality ...

Angular project models

I'm exploring the Core/Shared/Feature design pattern for building large, scalable applications in Angular, and I find myself unsure about where models fit in. Is there a consensus on which module they belong in? I came across a post suggesting that ...

Retrieve the accurate file name and line number from the stack: Error object in a JavaScript React Typescript application

My React application with TypeScript is currently running on localhost. I have implemented a try...catch block in my code to handle errors thrown by child components. I am trying to display the source of the error (such as file name, method, line number, ...

Sharing data between parent and child components in Angular using ngrx

Currently, I am implementing @ngrx router and facing a scenario where one of the routes includes child routers for passing route parameters. Here is how it looks: { path: '/browse', component: BrowseComponent, children: [ { path: ':ca ...

Sharing context sub files in Next.js can be easily accomplished by following a few simple

These are the pages on my website: /pages /gift /[slug] index.tsx /personalize index.tsx In /gift/[slug]/index.tsx, I have a component called GiftProvider that looks like this: return ( <GiftProvider gift={gift}& ...

The request from localhost:3000 to localhost:3003 could not be proxied by ReactJS

Currently, I am working on developing a Single Page Application (SPA) using create-react-app with an expressjs server as the backend. During development, my frontend test server runs on port 3000 while my backend expressjs test server runs on port 3003. T ...