🖼️

Solving for Content-Aware iframes in Angular

Solving for Content-Aware iframes in Angular

Now that we’ve set the context, let’s solve for one of the downsides typically associated with iframes: they’re not content-aware and bad for responsive design. We can fix that!
Our solution is simple and is based on a few standard browser APIs:
ResizeObserver — used to detect changes in content height / width by the application hosted inside the iframe.
window.postMessage — used to send a message to the parent window whenever the height / width of the embedded app changes.

Step 1: Create a service that notifies the parent window of height / width changes of the embedded app

Since a parent window cannot directly observe changes in content width / height of a cross origin iframe embedded application, we first we need to create a service in our embedded application that listens for changes in it’s own height / width and then sends that info to the parent window via a message event.
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class DocumentResizeMessageService {
  private resizeObserver: ResizeObserver | undefined;

  init(): void {
    const html = document.querySelector('html');

    if (html && window.parent) {
      this.resizeObserver = new ResizeObserver(
        (entries: ResizeObserverEntry[]) => {
          window.parent.postMessage({
            type: 'content-resize',
            contentRect: entries[0].contentRect,
          }, '*');
        }
      );
      this.resizeObserver.observe(html);
    }
  }
}

Step 2: Create a directive that listens for content resize messages

The next step is to create a directive in the parent application that listens to these events and automatically resizes the iframe element (it’s host) whenever it receives a message.
import { AfterViewInit, Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[iframeAutoResize]',
  exportAs: 'iframeAutoResize',
})
export class IframeAutoResizeDirective implements AfterViewInit {
  constructor(private el: ElementRef) {}

  ngAfterViewInit(): void {
    const iframeWindow = this.el.nativeElement.contentWindow;

    window.addEventListener('message', (message) => {
      if (
        message.source !== iframeWindow ||
        message.data.type !== 'content-resize'
      ) {
        return; // Skip message in this event listener
      }

      if (message.data.contentRect.height) {
        this.el.nativeElement.style.height = `${message.data.contentRect.height}px`;
      }

      if (message.data.contentRect.width) {
        this.el.nativeElement.style.width = `${message.data.contentRect.width}px`;
      }
    });
  }
}
<iframe iframeAutoResize src="https://foo.bar"></iframe>
 
An interactive demo of this concept (see screenshot below) is available to play around with on Github pages or on Stackblitz along with all source code for the parent application and embedded application on Github.