Kevin Burton
Kevin Burton

Reputation: 121

Stop angular reloading iframes when changing components?

I have an angular application which I built based on ngxadmin.

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

It's on Angular 4.4.6.

The app changes between different dashboards. Each of these dashboards has an iframe with some embedded charts.

The problem is that every time I change the dashboard, the iframe reloads.

The reload takes 1-2 seconds and isn't super fast when compared to the main angular app (which is entirely cached).

The problem is that every time an iframe is injected into the HTML it causes it to reload. The rendered content isn't preserved.

I've read a lot of places and this is a fundamental design of iframes. If you remove / insert them back into the DOM they are reloaded.

I've also tried to make them as FAST as possible by using HTTP caching and a CDN (Fastly). That improves the situation but I'm still faced with these slow load times.

Is there a way I can prevent the iframe from reloading every time?

Is there a way I can have angular not REMOVE the HTML content but instead just display:none it so that it's still actually part of the DOM?

Another idea I thought of was rendering the iframe hidden, then copying the body innerHTML and moving it into my angular app. Then just using that content. I'm not super concerned about security since I control both apps but I imagine the CSS would be broken at that point.

One idea is I could just write the pre-rendered HTML into the iframe instead of relying on fetching it from 'src' each time.

Upvotes: 12

Views: 11959

Answers (6)

freddymarinn
freddymarinn

Reputation: 1

Using a Map to store the sanitized URLs. This way, we can ensure the iFrame's src is only updated when the URL changes, preventing unnecessary reloads. I'm talking components in the following steps because i'm using angular.

In your component, create a new Map property to store the sanitized URLs:

export class AdviserComponent implements OnInit {
  // ...
  sanitizedUrls: Map<string, SafeResourceUrl> = new Map();
  // ...
}

Update the sanitized() function to use the new sanitizedUrls property:

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

Upvotes: 0

Sean Dillon
Sean Dillon

Reputation: 260

If you create a variable to be your sanitized url and sanitize it on ngoninit then you can reference that variable directly from your iframe without calling the sanitization function that causes a refresh.

Export class YourComponent {
  public sanitizedSource: SafeResourceUrl;
  private baseUrl = 'whateveryouwant.com';
  
  ngOnInit() {
    this.sanitizedSource = this.sanitizer.bypassSecurityTrustResourceUrl(
      this.baseUrl)
  }
  
  getSourceURL(): SafeResourceUrl {
    return this.sanitizedSource;
  }
}

And in your html call the function like this

<iframe [src]='getSourceURL()'></iframe>

Upvotes: 6

Andrew_W
Andrew_W

Reputation: 31

I had a similar issue, using Angular 12. However, I couldn't use a directive, as the HTML was loaded from an external source. Furthermore, detaching the change detection stopped the iframes from loading altogether. Using OnPush Change detection strategy worked perfectly.

Upvotes: 2

AdrianM
AdrianM

Reputation: 175

I had a similar issue. A simple caching of the sanitized url object fixed it. See https://github.com/angular/angular/issues/16994#issue-231099513

Upvotes: 0

Ernstjan Freriks
Ernstjan Freriks

Reputation: 671

I don't know if my problem is exactly the same as yours, but I've had an issue when using the sanitizer.bypassSecurityResourceUrl as the src for the iframe:

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

'url' here is of type string. This resets the iframe in every changeDetection. To overcome this a custom directive could be used:

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

This is the code for the directive:

@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
        ) { }
}

As long as the iframe is in the DOM tree and the src is not changed, this will work I guess. No need for bypassing URL's using the DomSanitizer either :)

Upvotes: 19

Eric Van Der Dijs
Eric Van Der Dijs

Reputation: 136

sorry for the late response, but I've encountered the same problem when embedding youtube videos on mi app. I wasn't able to fullscreen the embedded videos due to some on resize handler being called which triggered change detection and reloaded the iframe.

Anyway, I managed to solve this issue using the ChangeDetectorRef from @angular/core which seems to be available since angular 2.

what I did was:

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()
  }
}

I know this is a workaround, but is not like you are going to detach your whole application, you are only preserving your already loaded iframe. Additionally you'll remove that little piece anyway when the rest of your template updates.

Hope this helps you or others with the same issue.

Upvotes: 8

Related Questions