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.
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 script
s, embed
s, object
s, and iframe
s 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.