In my Angular application, I have the following invariable basic conditions:
data:image/jpeg;base64,...
-like sources (but no external links).I managed to implement 1. and 2. seamlessly with JSZip etc. Thus, we can assume, there's a String
variable htmlFileContent
now. Displaying the iFrame on specific conditions in an otherwise empty Angular tab is also not the problem, I managed to do this. To achieve the rest of point 3, I see two different approaches:
Using a div:
Use htmlFileContent
as the innerHTML
of a div
element, like so:
<div [innerHtml]="{{ htmlFileContent }}"></div>
This works indeed, but has some umcomely side effects, e. g. the title
of the "inner" HTML header is rendered as well in the browser. Hence, I could try and parse htmlFileContent
into a DOM and remove the unwanted tree elements. This might be a working solution, but somehow I don't feel good about it.
Additionally, the HTML contains some in-file anchor links (<a href="#topOfPage">
style) which would also not work any more and need to be corrected.
Using an iFrame:
I know well about the uglyness and deprecation of iFrame usage, nevertheless, in my eyes this seems to be an adequate approach to my problem. So I would use:
<iframe [src]="fileUrl" class="fullscreenIFrame"></iframe>
And here the problem arises: One could set fileUrl="data:text/html;charset=utf-8,"+ htmlFileContent
. But then, Angular will (rightly) complain: ERROR Error: NG0904: unsafe value used in a resource URL context (see https://g.co/ng/security#xss)
and nothing will be displayed. So, currently I am trying something like this using the DomSanitizer
from @angular/platform-browser
:
this.filedata = this.sanitizer.bypassSecurityTrustHtml(htmlFileContent);
this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl("data:text/html;charset=utf-8,"+ this.filedata) ;
Which will indeed work partly: I get a warning SafeValue must use [property]=binding:
rendered in the output and the "inner" HTML is cut where the first src="data:..."
image appears.
And here I'm stuck. My iFrame can, of course, have only one src
and I can't concatenate fileUrl
(which then would be shortened to the data:text/html;charset=utf-8,
content) and fileData
there as one is a SafeResourceUrl
and one a SafeHtml
object.
Here's a MWE of my problem:
https://stackblitz.com/edit/angular-ivy-uzpcsy?file=src/app/app.component.ts
(Interestingly, the image is rendered here... Anyway, the SafeValue
warning persists.)
Do you have any suggestions on how to handle this particular requirement? Might the div
-approach still be the better one? Any help is greatly appreciated - many thanks!
As I didn't receive or find another possibility, this is the way I implemented it - just for future reference:
import { ActivatedRoute, Router, UrlSegment } from '@angular/router';
import { DomSanitizer, SafeHtml, SafeResourceUrl } from '@angular/platform-browser';
export class MyComponent implements OnInit {
filedata: SafeHtml = 'Loading...';
currentUrl: string = '';
constructor(
private activatedRoute: ActivatedRoute,
private sanitizer: DomSanitizer,
) {}
ngOnInit(): void {
// Use the Angular possibilities to retrieve to complete current URL
this.activatedRoute.url.subscribe(url => {
this.currentUrl = '';
url.forEach(urlElement => {
this.currentUrl += urlElement + '/';
});
this.currentUrl = this.currentUrl.substring(0, this.currentUrl.length - 1);
});
this.loadFileData();
}
loadFileData(): void {
// This is the function where the ZIP file is downloaded and the file
// content is extracted. As it is not part of the question or the
// answer, I will not post it here.
// Let's just assume, the file content is now stored in "response".
this.filedata =
this.sanitizer.bypassSecurityTrustHtml(this.correctFileData(response));
}
// Receives the file content response as first parameter,
// removes the unwanted tags and corrects the anchor links.
private correctFileData(response: string): string {
let el = document.createElement('html');
el.innerHTML = response;
// Remove all "head" tags including their inner elements
// (Of course, the should be only one head tag, but you never know...)
let headElements = el.getElementsByTagName('head');
for (let i = 0; i < headElements.length; i++) {
el.removeChild(headElements[i]);
}
// Correct all anchor links: Prepend the current URL
// So http://my.address.com/#anchor would become
// http://my.address.com/myApp/myRoute/mySubRoute#anchor
// for example
let links = el.getElementsByTagName('a');
for (let i = 0; i < links.length; i++) {
let link = links[i];
if (link.href.indexOf('#') != -1) {
console.log(link.href + ' --> ' + this.currentUrl +
link.href.substring(link.href.indexOf('#')));
link.href = this.currentUrl + link.href.substring(link.href.indexOf('#'));
}
}
return el.outerHTML;
}
And in the component's HTML, I just use:
<div [innerHTML]="filedata"></div>
Possibly, there are other, better solution, but this one works very well for my use case.