angularpdf

How do I render a PDF document being served from a Spring Boot API in an Angular app?


I have a Spring Boot application that presents an API of the form GET /api/file/{fileId}. This returns the file contents as a byte[] with application/pdf content-type.

I have an Angular (18) app in which I want to render the file contents.

I have tried embed, object, and iframe. In each case, I get nothing (Firefox), a grey rectangle (Safari) or a white rectangle (Chrome). and in each case I can see that the browser is not making the request to the file API.

<embed [src]="pdfUrl" type="application/pdf" width="100%" height="500" />
<object [data]="pdfUrl" type="application/pdf" width="100%" height="500"></object>
<iframe [src]="pdfUrl" type="application/pdf" width="100%" height="500"></iframe>

all have, essentially, the same result in all three browsers.

pdfUrl is a property on the component, derived from an object that has the API URL to the file. If I open the file directly, it definitely renders in the browser and also in Postman.

<a [href]="pdfUrl">{{file.displayName}}</a>

This should not be a cross-origin problem. The angular app is being served from the same base URL as the API (/, vs /api for the Spring Boot app. http://localhost:8080/ while in development). As far as I can tell, rendering a PDF inline does not pose a security risk to modern browsers.

Does the URL need to be HTTPS nowadays? Is there something that needs to be enabled in the browser?

I cannot/do not want to use the Google Docs render method for a whole host of reasons (not least being that the PDF URLs are not public), and I would rather not use a JavaScript library to render the PDFs unless I really must, less code being better than more.


Solution

  • I am answering my own question. There is an error in the logs - I only saw its relevance while researching everything to ensure I write my question properly. (StackOverflow as Rubber Duck for the win!).

    The error I get is NG0904.

    This is an Angular thing, not a browser thing. Angular inherently considers URLs passed through code for scripts, embeds, objects, and iframes etc. as "unsafe".

    To resolve this, the URL needs to be marked as "safe". You can do this programmatically using the Angular's DomSanitizer. I used safe-pipe, which is a pipe that wraps the sanitiser's methods to use directly in the HTML. Note, the sanitiser is bypassing Angular's security mechanism. This is only OK because I as the developer know that the URL is safe.

    Install safe-pipe with

    npm install safe-pipe
    

    Then, mark the URL as safe using the pipe pdfUrl | safe: 'resourceUrl'. e.g.

    <embed [src]="pdfUrl | safe: 'resourceUrl'" type="application/pdf" width="100%" height="500" />
    <object [data]="pdfUrl | safe: 'resourceUrl'" type="application/pdf" width="100%" height="500"></object>
    <iframe [src]="pdfUrl | safe: 'resourceUrl'" type="application/pdf" width="100%" height="500"></iframe>
    

    The safe pipe takes an argument that tells it which method to use from the DomSanitizer. The value of resourceUrl indicates it is for a URL to a resource being loaded into the DOM.