8

Goal/Background: I need to send the value of a variable from my Angular application to the ChatServer(aspx with javascript) that is inside an iFrame on the page. It is on a different server.

What I've tried: I am following the workaround here: https://stackoverflow.com/a/25098153/11187561

However, I am getting the error: Property 'contentWindow' does not exist on type HTMLElement

What else i've tried next: Looking through SO, I found https://stackoverflow.com/a/38457886/11187561

I placed it in ngAfterViewInit but I am still getting the error.

Code:

ngAfterViewInit() {
    var frame = document.getElementById('your-frame-id');
    frame.contentWindow.postMessage(/*any variable or object here*/, '*'); 
}
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
angleUr
  • 449
  • 1
  • 8
  • 27

4 Answers4

18

The problem is that getElementById returns an HTMLElement not an HtmlIFrameElement. What you can do is define a type guard to check that the frame is an IFRAME. A secondary problem is that contentWindow can be null, so we also must check that.

const isIFrame = (input: HTMLElement | null): input is HTMLIFrameElement =>
    input !== null && input.tagName === 'IFRAME';

function ngAfterViewInit() {
    let frame = document.getElementById('your-frame-id');
    if (isIFrame(frame) && frame.contentWindow) {
        frame.contentWindow.postMessage({}, '*');
    }
}
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • Thanks. This does result in the following error though: "Error: unsafe value used in a resource URL context." I am reading up about XSS to see how to avoid this – angleUr Jun 12 '19 at 15:39
  • @angleUr Have you read the MDN page? It's usually helpful for me. https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#Security_concerns You're no longer experiencing an error with TypeScript but instead an error with how the `postMessage` API works. – Shaun Luttin Jun 12 '19 at 15:51
  • 1
    I see this deserves a separate post as the error is different now. I looked through the documentation and updated my code in a separate thread – angleUr Jun 12 '19 at 23:04
9

If you are receiving that from Typescript type validation, you can explicit tell the compiler the type you want doing like this:

var iframe = (document?.querySelector('#myIframe') as HTMLIFrameElement) 
Tiago Barroso
  • 329
  • 4
  • 4
4

HTMLIFrameElement

just mention the type

const docs = (document.getElementById('pdfDocument') as HTMLIFrameElement);
Aayush Bhattacharya
  • 1,628
  • 18
  • 17
3

You can do this with a generic query selector like so:

const frame = document.querySelector<HTMLIFrameElement>("#your-frame-id")

Typescript will then return a nullable type, which makes sense because it's not guaranteed this element will be found. You can get the contentWindow window off the nullable object and then safely process it only if it's not null/undefined e.g.

const window = frame?.contentWindow
if(!!window){
    //You can do stuff now with window
}
else{
    throw new Error("Problem retrieving frame")
}

Notes

This is arguably slightly neater compared to Tiago's answer and Aayush's answer (as they are now at the time of writing). In particular, the type returned is nullable, forcing you to handle that case. However, it must be noted that since Typescript erases types at runtime, all three answers are no better at runtime than simply doing this:

const frame = document.querySelector("#your-frame-id")

See this codesandbox for proof. Sure, it has a compile time error highlighted - but it runs just fine. Here's the code:

import "./styles.css";

const app = document.getElementById("app");

if (!!app) {
  app.innerHTML = `
<h1>Iframe Tester</h1>
<iframe id='x'></iframe>`;
}

const frame = document.querySelector("#x");
const window = frame?.contentWindow;
if (!!window) {
  const child = document.createElement("p");
  child.innerHTML = "Window found with origin: " + window.origin;
  app?.appendChild(child);
} else {
  console.log("Problem retrieving frame");
}

Importantly, this means if a non-iframe with the given ID is found, it'll be returned by the query; the query will not return null in this case. See this codesandbox for proof. Here's the code:

import "./styles.css";

const app = document.getElementById("app");

if (!!app) {
  app.innerHTML = `
<h1>Iframe Tester</h1>
<p id='x' hidden>I'm not a frame, I'm a paragraph!</p>`;
}

const frame = document.querySelector<HTMLIFrameElement>("#x");
if (!!frame) {
  const child = document.createElement("p");
  child.innerHTML = "Frame found with inner HTML: " + frame.innerHTML;
  app?.appendChild(child);
} else {
  console.log("Problem retrieving frame");
}

Of course, in the main part of this answer, the access to the .contentWindow will help rule out the case where there's a non-iframe element with the given ID- if this is undefined, then the else branch will be taken. However, Shaun's answer may be safer as it dynamically checks the tagname and confirms at runtime that an iframe was found.

Note: the idea for my answer came from CertainPerformance's answer to a different SO Post. That post deals with the general problem of casting HTML elements in Typescript.

Colm Bhandal
  • 3,343
  • 2
  • 18
  • 29