在 Angular 中动态创建和嵌入数据 URL

Dynamically create and embed data URL in Angular

在我的Angular申请中,我有以下不变的基本条件:

  1. 应用程序从外部动态加载 ZIP 文件 URL。
  2. 在此 ZIP 文件中,有一个要提取的 HTML 文件。此 HTML 文件可能包含具有 data:image/jpeg;base64,... 类来源的图像(但没有外部链接)。
  3. HTML 文件必须由 Angular 应用程序在单独的浏览器选项卡中“按原样”显示

我设法使用 JSZip 等无缝地实现了 1. 和 2. 因此,我们可以假设,现在有一个 String 变量 htmlFileContent。在特定条件下在空的 Angular 选项卡中显示 iFrame 也不是问题,我设法做到了这一点。为了实现第 3 点的其余部分,我看到了两种不同的方法:

使用 div:

使用 htmlFileContent 作为 div 元素的 innerHTML,像这样:

<div [innerHtml]="{{ htmlFileContent }}"></div>

这确实有效,但有一些副作用,例如。 G。 “内部”HTML header 的 title 也在浏览器中呈现。因此,我可以尝试将 htmlFileContent 解析为 DOM 并删除不需要的树元素。这可能是一个可行的解决方案,但不知何故我对此感觉不太好。

此外,HTML 包含一些 in-file 锚链接(<a href="#topOfPage"> 样式),这些链接也不再有效,需要更正。

使用 iFrame:

我很清楚 iFrame 使用的丑陋和弃用,但是,在我看来,这似乎是解决我的问题的适当方法。所以我会使用:

<iframe [src]="fileUrl" class="fullscreenIFrame"></iframe>

这里出现了问题:可以设置 fileUrl="data:text/html;charset=utf-8,"+ htmlFileContent。但是,Angular 会(正确地)抱怨:ERROR Error: NG0904: unsafe value used in a resource URL context (see https://g.co/ng/security#xss) 并且不会显示任何内容。所以,目前我正在尝试使用来自 @angular/platform-browser:

DomSanitizer
this.filedata = this.sanitizer.bypassSecurityTrustHtml(htmlFileContent);
this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl("data:text/html;charset=utf-8,"+ this.filedata) ;

这确实会工作 部分:我在输出中收到警告 SafeValue must use [property]=binding: 并且“内部”HTML 在第一个 src="data:..." 图像出现。

我卡在这里了。当然,我的 iFrame 可以只有一个 src,我不能将 fileUrl(然后将缩短为 data:text/html;charset=utf-8, 内容)和 fileData 连接为一个是一个 SafeResourceUrl 和一个 SafeHtml object.


这是我的问题的 MWE:

https://stackblitz.com/edit/angular-ivy-uzpcsy?file=src/app/app.component.ts

(有趣的是,图像是在这里渲染的...无论如何,SafeValue 警告仍然存在。)

对于如何处理这个特殊要求,您有什么建议吗? div-方法可能仍然是更好的方法吗?非常感谢任何帮助 - 非常感谢!

由于我没有收到也没有找到另一种可能性,所以这是我实现它的方式 - 仅供将来参考:

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

而在组件的 HTML 中,我只使用:

<div [innerHTML]="filedata"></div>

可能还有其他更好的解决方案,但这个解决方案非常适合我的用例。