更换组件时停止Angular重新加载iframe?

11
我有一个基于ngxadmin构建的Angular应用程序。

https://github.com/akveo/ngx-admin

它是在Angular 4.4.6上运行的。
该应用程序在不同的仪表板之间切换。每个仪表板都有一个带有一些嵌入式图表的iframe。
问题在于,每次更改仪表板时,iframe都会重新加载。
与主要的Angular应用程序(完全缓存)相比,重新加载需要1-2秒钟,并且不是超级快速的。
问题在于每次将iframe注入到HTML中时,它会导致重新加载。渲染的内容不会被保留。
我已经在很多地方阅读过这个问题,这是iframes的基本设计。如果您将它们从DOM中删除/插入回去,它们将重新加载。
我还尝试了使用HTTP缓存和CDN(Fastly)使它们尽可能快。这改善了情况,但我仍然面临着这些慢速加载时间。
有没有办法可以防止每次重新加载iframe?
有没有办法让angular不删除HTML内容,而只是将其显示为none,以便它仍然实际上是DOM的一部分?
另一个想法是将iframe渲染隐藏,然后复制body innerHTML并将其移动到我的angular应用程序中。然后只需使用该内容。我不是特别担心安全性,因为我控制两个应用程序,但我想在这一点上CSS会出问题。
一个想法是我可以将预渲染的HTML直接写入iframe,而不是每次从“src”获取它。

Angular 应该作为单页应用程序使用,不应使用 iframes。在我看来。 - snap
我同意,但我没有时间重建复杂的图表或需要长期支持它。从使用iframes的Grafana嵌入图表更容易。 - Kevin Burton
6个回答

19

我不知道我的问题是否与你的完全相同,但我在使用sanitizer.bypassSecurityResourceUrl作为iframe的src时遇到了问题:

<iframe [src]="sanitizer.bypassSecurityTrustStyle(url)"></iframe>

'url'在此是字符串类型。这将在每次changeDetection中重置iframe。为了解决这个问题,可以使用自定义指令:

“url”在此为字符串类型。每次发生变化检测时,都会重置iframe。为了克服这个问题,可以使用自定义指令:

<iframe [cachedSrc]="url"></iframe>

这是指令的代码:

@Directive({
  selector: 'iframe'
})
export class CachedSrcDirective {

    @Input() 
    public get cachedSrc(): string {
        return this.elRef.nativeElement.src;
    }
    public set cachedSrc(src: string) {
        if (this.elRef.nativeElement.src !== src) {
            this.renderer.setAttribute(this.elRef.nativeElement, 'src', src);
        }
    }

    constructor(
        private elRef: ElementRef,
        private renderer : Renderer2
        ) { }
}
只要 iframe 在 DOM 树中且 src 未更改,我想这将起作用。也不需要使用 DomSanitizer 绕过 URL :)

2
很高兴看到我不是唯一遇到iframe问题的人。在Angular 8中仍然存在... - Maxime Lafarie
1
我无法添加这行代码 <iframe [cachedSrc]="url"></iframe>。我需要在某个地方导入它吗?请解释一下。我收到了这个错误:Can't bind to 'cachedSrc' since it isn't a known property of 'iframe'. - Sanal S
这个方法非常好用!但是能再解释一下吗?你说“只要iframe在DOM树中且src未更改,它就会起作用”,但我已经尝试了动态URL并不断更改URL(也就是更改src属性),它仍然可以正常工作!所以即使更改了src属性,它也可以正常工作!我不明白为什么使用DomSanitizer会导致这种问题! - SlimenTN
这在 Angular 10 中不起作用,你能帮我吗?谢谢。 - Jimit H.
这在 Angular 12 中可以工作。您必须将该新指令作为“Declarations”导入到您的 app.module.ts 中。 - Hans Daigle
让我开心了一整天!谢谢。我花了很长时间尝试从changeDetector中分离(detach()),而这正是我正在寻找的解决方案。 - VnC

8

很抱歉回复晚了,但我在将Youtube视频嵌入我的应用程序时遇到了相同的问题。由于调用了某些调整大小处理程序触发了变更检测并重新加载iframe,我无法全屏嵌入式视频。

无论如何,我使用了@angular/core中提供的ChangeDetectorRef解决了这个问题,似乎自Angular 2以来就可用。

我所做的是:

import { Component, ChangeDetectorRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-your-component',
  templateUrl: './your-component.component.html', // here goes your iframe
  styleUrls: ['./your-component.component.scss']
})
export class YourComponent implements AfterViewInit {

  constructor( private ref: ChangeDetectorRef ) { }

  ngAfterViewInit() {
    this.ref.detach()
  }
}

我知道这只是一个权宜之计,但你不需要分离整个应用程序,只需保留已加载的iframe即可。此外,当模板的其余部分更新时,您将删除那个小片段。
希望这能帮助您或其他遇到同样问题的人。

1
在我的情况下非常完美(组件仅包含<iframe>元素),谢谢!但是如果您需要在此之后处理一些事件或动态数据绑定,请小心。 - Maxime Lafarie
2
如果您在组件内部加载iframe,则所有变更检测机制都会与该页面分离。 - Manoj Gupta
我在使用一个预设主题,但我的 iframe 因为鼠标经过和离开事件而一直重新加载。这对我来说非常有效。谢谢。 - Ashique Razak

2
如果你创建一个变量作为经过清理的URL,并在ngOnInit中进行清理,那么你可以直接从iframe中引用该变量,而不需要调用导致刷新的清理函数。
Export class YourComponent {
  public sanitizedSource: SafeResourceUrl;
  private baseUrl = 'whateveryouwant.com';
  
  ngOnInit() {
    this.sanitizedSource = this.sanitizer.bypassSecurityTrustResourceUrl(
      this.baseUrl)
  }
  
  getSourceURL(): SafeResourceUrl {
    return this.sanitizedSource;
  }
}

在你的HTML中像这样调用函数。
<iframe [src]='getSourceURL()'></iframe>

2

我遇到了类似的问题,使用Angular 12。然而,由于HTML是从外部源加载的,我无法使用指令。此外,分离变更检测会完全停止iframes的加载。使用OnPush变更检测策略可以完美解决问题。


你能提供一个例子吗? - undefined

0

0
使用Map来存储经过处理的URL。这样,我们可以确保只有在URL更改时才更新iFrame的src,从而防止不必要的重新加载。我在下面的步骤中提到组件,因为我正在使用Angular。
在您的组件中,创建一个新的Map属性来存储经过处理的URL:
export class AdviserComponent implements OnInit {
  // ...
  sanitizedUrls: Map<string, SafeResourceUrl> = new Map();
  // ...
}

更新sanitized()函数以使用新的sanitizedUrls属性:

sanitized(url: string | undefined): SafeResourceUrl | null {
  if (url === undefined) {
    return null;
  } else {
    let sanitizedUrl = this.sanitizedUrls.get(url);
    if (!sanitizedUrl) {
      sanitizedUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
      this.sanitizedUrls.set(url, sanitizedUrl);
    }
    return sanitizedUrl;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接