0

I have an Angular application where I need to be able to have multiple iframe's loaded. The web pages I load in the iframes is also build using Angular. If I write my HTML like this

<div id="frame">
    <div>
        <iframe id='iframeId0' [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
    </div>
    <div>
        <iframe id='iframeId1' [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
    </div>
    <div>
        <iframe id='iframeId2' [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
    </div>
    <div>
        <iframe id='iframeId3' [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
    </div>
    <div>
        <iframe id='iframeId4' [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
    </div>
</div>

I can use postMessage to send data to the iframes like this:

ngOnInit() {
   for (let i = 0; i < this.numberOfIframes; i++) {
      this.iframeData.push({ url: '', iframeNumber: i });
   }
   this.dbService.dbData$.subscribe(dbData => {const message: PmData = {
      command: 'url
      payload: url,
   }
   const win = window.frames[this.pdfFrame];
   win.postMessage(JSON.stringify(message), environment.docViewerUrl);

dbData$ is an observable to firestore data. The data is received in the iframe by an other Angular app where the event listener is added like this:

ngOnInit() {
    window.addEventListener("message", async (event) => {
       console.log('called from', event.origin);

and it works fine.

If I use a *ngFor like this:

<div *ngFor="let frame of iframeData">
    <iframe [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
</div> 

and send postMessage like above, I never receive any message. Why is that and how can I fix it?

UPDATE

As pr @ConnorsFan suggestion I tried the following:

<div id="frame">
<div #iframeContainer *ngFor="let iframe of iframeData">
    <div [hidden]="pdfFrame !== 0">
        <iframe [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
    </div>
</div>

export class OneComponent implements OnInit, AfterViewInit {
@ViewChildren('iframeContainer') iframeContainers: QueryList<ElementRef>;

sanitizedURL: SafeResourceUrl;
pdfFrame = 0;
docIdx = 0;
iFrameHeight = '';
numberOfIframes = 5;
dbData: DbData;
pageNumber = 1;
pdfLoading = false;
iframeData: IframeData[] = [];


constructor(
    private sanitizer: DomSanitizer,
    private dbService: DbService
) { }

ngOnInit() {
    console.log('one ngOnInit');

    for (let i = 0; i < this.numberOfIframes; i++) {
        this.iframeData.push({ url: '', iframeNumber: i });
    }

    const docId = document.getElementById('frame'); // I can't get height=100% to work in the HTML
    this.iFrameHeight = `${docId.clientHeight - 20}px`;

    this.sanitizedURL = this.sanitizer.bypassSecurityTrustResourceUrl(environment.docViewerURL);

}

async ngAfterViewInit() {

    this.dbData = await this.dbService.dbData$.pipe(first()).toPromise();

    //await this.iframeContainers.changes.pipe(first()).toPromise();

    this.iframeContainers.changes.subscribe((list: QueryList<ElementRef>) => {
        console.log('iframeContainers.changes', list);

    });
    this.handleLoadedDocs(this.dbData.docs[this.pdfFrame].downloadURL);

    this.dbService.dbData$.subscribe(dbData => this.dbData = dbData);

    window.addEventListener('message', async (event) => {
        if (typeof event.data === 'string') {
            const responce = JSON.parse(event.data);
            if (responce.command === 'PdfLoaded') {
                this.pdfLoading = false;
            }
        }
    });
}

private handleLoadedDocs(url: string) {
    if (this.pdfLoading) return;

    const idx = this.iframeData.findIndex(data => data.url === url);
    if (idx >= 0) {
        // URL is already loaded, make it active
        this.pdfFrame = this.iframeData[idx].iframeNumber;
        // and move it to the top array position
        const topIframe = this.iframeData.splice(idx, 1);
        this.iframeData.push(topIframe[0]);
    } else {
        const oldIframe = this.iframeData.shift();
        this.iframeData.push({ url: url, iframeNumber: oldIframe.iframeNumber });
        this.pdfFrame = oldIframe.iframeNumber;

        // send URL to doc-viewer
        this.sendCommandToPdfViewer(PdfViewerCommand.URL, url);
    }
    console.log('this.iframeData', this.iframeData);
}

private sendCommandToPdfViewer(command: PdfViewerCommand, payload: any) {
    const message = {
        command,
        payload,
    }
    const win = window.frames[this.pdfFrame];
    win.postMessage(message, environment.docViewerURL);
}

With this code I get the following output in the log:

QueryList {dirty: false, _results: Array(5), changes: EventEmitter, length: 5, last: ElementRef, …}
    changes: EventEmitter {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}
    dirty: false
    first: ElementRef {nativeElement: div}
    last: ElementRef {nativeElement: div}
    length: 5
    _results: (5) [ElementRef, ElementRef, ElementRef, ElementRef, ElementRef]
    __proto__: Object

, but if I move this.handleLoadedDocs(this.dbData.docs[this.pdfFrame].downloadURL); into the iframeContainers subscription (which is what I guess I need to do?) I don't get anything in the log. So it look like I have to send something to get the the iframeContainers subscription to emit. SO what am I missing?

Jørgen Rasmussen
  • 1,143
  • 14
  • 31
  • Can you show the context of the code snippets? In which method do you call `postMessage` and `addEventListener`? – ConnorsFan Feb 12 '19 at 14:12
  • I added som context to the question – Jørgen Rasmussen Feb 12 '19 at 14:46
  • 1
    The view is not rendered yet in `ngOnInit`. Do you see any error message in the console? I would expect `win` to be `undefined`. – ConnorsFan Feb 12 '19 at 15:20
  • No, I don't get any error message. Is there another lifecycle hook I should use? – Jørgen Rasmussen Feb 12 '19 at 15:22
  • 1
    You can try `ngAfterViewInit`. You may need to handle the `QueryList.changes` event since the `iframe`s are created in an `ngFor` loop. See these answers: [1](https://stackoverflow.com/a/53124292/1009922), [2](https://stackoverflow.com/a/50306090/1009922). – ConnorsFan Feb 12 '19 at 15:25

1 Answers1

0

I think I figured it out, at least this solution works in Chrome, Firefox and Safari on Mac.

My HTML:

<div id="frame">
    <div  *ngFor="let dummy of ' '.repeat(numberOfIframes).split(''); let i = index">
        <div [hidden]="activeFrame !== i">
            <iframe #iframeContainer [src]=sanitizedURL [height]="iFrameHeight" width="100%" frameborder="0"></iframe>
        </div>
    </div>
</div>

In the .ts file:

export class OneComponent implements OnInit, AfterViewInit {
   @ViewChildren('iframeContainer') iframeContainers: QueryList<ElementRef>;
   ...
   ngAfterViewInit() {

        this.iframeContainers.forEach(_iframe => {
            this.iframeRefs.push(_iframe);
        });

Then I can communicate with each iframe:

const win = this.iframeRefs[iframeIdx].nativeElement.contentWindow;
win.postMessage(message, environment.docViewerURL);
Jørgen Rasmussen
  • 1,143
  • 14
  • 31