iframe-resizer icon indicating copy to clipboard operation
iframe-resizer copied to clipboard

Guidelines for consuming this library in angular 2

Open adaneam opened this issue 7 years ago • 46 comments

Could you please provide guidelines for using this library in angular 2 project created with CLI/Webpack.

adaneam avatar Apr 04 '17 09:04 adaneam

I'm React rather than Angular 2. If you get it working please let me know and I'll add something to the docs. I suggest asking StackOverflow.

davidjbradshaw avatar Apr 04 '17 10:04 davidjbradshaw

I need the same. Please help us.

lucasnichele avatar Jun 05 '17 20:06 lucasnichele

Maybe this will help you. First declare types in "typings.d.ts". Here is an example, please notice that I only added methods that I needed.

interface IFrameObject {
    close();
    resize();
}

interface IFrameResizerComponent {
    iFrameResizer: IFrameObject
}

declare const resizer: {
    iframeResizer(options: any, target: HTMLElement);
};

/* Modules */
declare module 'iframe-resizer' {
    export = resizer;
}

After that, you should create directive. Something like this.

import {AfterViewInit, Directive, ElementRef, OnDestroy} from '@angular/core';
import * as resizer from 'iframe-resizer';

@Directive({
    selector: 'iframe.thread-content'
})
export class IFrameResizerDirective implements AfterViewInit, OnDestroy {
    component: IFrameResizerComponent;

    constructor(public element: ElementRef) {
    }

    ngAfterViewInit() {
        this.component = resizer.iframeResizer({
            checkOrigin: false,
            heightCalculationMethod: 'documentElementOffset',
            log: false
        }, this.element.nativeElement);
    }

    ngOnDestroy(): void {
        if (this.component && this.component.iFrameResizer) {
            this.component.iFrameResizer.close();
        }
    }
}

Pay attention to tag and tag class, this is used as a selector in created directive.

<iframe #messageIframe
        class="thread-content"
        [srcdoc]="processedDocument"
        title="Thread Content"
        scrolling="no" frameborder="0"></iframe>

I know that I am late, but maybe someone will find this useful.

arminbaljic avatar Jun 12 '17 14:06 arminbaljic

Does anyone have advice for consuming the contentWindow portion of it from angular2?

gausie avatar Jun 14 '17 11:06 gausie

@gausie I've not tried, but would expect it just to work. It's just a service running in the background on the page. If your using es6/TypeScript then I think you just need to import it.

If it is more complex than that then when you get the solution please share here.

davidjbradshaw avatar Jun 14 '17 12:06 davidjbradshaw

Everything is working fine - had a little issue with tree-shaking but this works fine:

import { iframeResizerContentWindow } from 'iframe-resizer';

// This needs to be referenced so it isn't tree-shaken.
iframeResizerContentWindow; // tslint:disable-line no-unused-expression

gausie avatar Jun 14 '17 14:06 gausie

That sounds like a bug with tree-shaking. Might be worth reporting over at WebPack.

davidjbradshaw avatar Jun 14 '17 14:06 davidjbradshaw

It would be great if @arminbaljic or someone else in the Angular community would publish an Angular wrapper to this project, like we have with https://github.com/davidjbradshaw/iframe-resizer-react

davidjbradshaw avatar Jun 18 '17 13:06 davidjbradshaw

Hi, @davidjbradshaw I have created a repository for angular wrapper: https://github.com/arminbaljic/ngx-iframe-resizer. This week basic version should be completed and later I could add some examples. Anyone who wants to join me is welcome.

arminbaljic avatar Jun 18 '17 19:06 arminbaljic

I have created pull request for type definitions. After this is merged I will start working on the wrapper.

Update: Types are published to the @types scope on NPM, you can install them using: npm install @types/iframe-resizer --save-dev

arminbaljic avatar Jun 27 '17 15:06 arminbaljic

@arminbaljic It would be great to see this wrapper working. I'm trying to make your directive working but I'm not able... Thanks!

alfupe avatar Aug 25 '17 11:08 alfupe

+1

lasimone avatar Oct 11 '17 14:10 lasimone

+2

zvaryuga avatar Nov 21 '17 22:11 zvaryuga

Maybe this will help, more detailed explanation on how to use the library in Angular (2,4,5). First, you need install "typings" for the iframe-resizer library. In order to do that, run this command:

npm install @types/iframe-resizer --save-dev

After that, you need to create a directive that will apply iframe-resizer to the iframe. Here is an example:

import {AfterViewInit, Directive, ElementRef, OnDestroy} from '@angular/core';
import {IFrameComponent, iframeResizer} from 'iframe-resizer';

@Directive({
    selector: '[appIframeResizer]'
})
export class IFrameResizerDirective implements AfterViewInit, OnDestroy {
    component: IFrameComponent;

    constructor(public element: ElementRef) {
    }

    ngAfterViewInit() {
        const components = iframeResizer({
            checkOrigin: false,
            heightCalculationMethod: 'documentElementOffset',
            log: false
        }, this.element.nativeElement);

        /* save component reference so we can close it later */
        this.component = components && components.length > 0 ? components[0] : null;
    }

    ngOnDestroy(): void {
        if (this.component && this.component.iFrameResizer) {
            this.component.iFrameResizer.close();
        }
    }
}

Add a directive to your iframe:

<iframe appIframeResizer
        class="thread-content"
        [srcdoc]="processedDocument"
        title="Thread Content"
        scrolling="no" frameborder="0"></iframe>

And the last thing you should do is to add a tag that will load "iframeResizer.contentWindow.js" in iframe. In order to do this, copy "iframeResizer.contentWindow.js" file to assets/js folder.

There are different ways to append the script to iframe content. Personally, I used "DOMPurify" plugin to sanitize and convert a string to HTML. Then, create the script tag and append it to the content of iframe document:

export class MessagePreviewComponent {
    @Input() document: string;

    constructor(private sanitizer: DomSanitizer) {
    }

    get processedDocument(): SafeHtml {
        if (this.document) {
            const sanitized = DOMPurify.sanitize(this.document, {FORBID_TAGS: ['script'], RETURN_DOM: true});

            /* Add script tag */
            const script = document.createElement('script');
            script.src = 'assets/js/iframeResizer.contentWindow.js';
            sanitized.appendChild(script);

            /* Return result */
            return this.sanitizer.bypassSecurityTrustHtml(sanitized.outerHTML);
        }
        return null;
    }
}

I hope this will help you :)

arminbaljic avatar Nov 29 '17 18:11 arminbaljic

There is a simpler version of "MessagePreviewComponent" that doesn't use DOMPurify plugin.

export class MessagePreviewComponent {
    @Input() document: string;

    constructor(private sanitizer: DomSanitizer) {
    }

    get processedDocument(): SafeHtml {
        if (this.document) {
            /* Add script tag */
            const script = document.createElement('script');
            script.src = 'assets/js/iframeResizer.contentWindow.js';

            /* Return result */
            const content = this.document.concat(script.outerHTML);
            return this.sanitizer.bypassSecurityTrustHtml(content);
        }
        return null;
    }
}

But please notice that this is a security risk. Use this version only if you are 100% sure that content of the iframe will not contain any harmful scripts.

arminbaljic avatar Nov 29 '17 19:11 arminbaljic

@arminbaljic thanks for the input on that. The only comment I have is this line.

this.component = components && components.length > 0 ? components[0] : null;

If you have more than one iFrame on the page that will loose the reference all but the first iFrame. So I think it would be better to just dothis.component = components here

davidjbradshaw avatar Nov 30 '17 14:11 davidjbradshaw

@davidjbradshaw In my case, only one iframe can be active on the page. The directive is directly used on the "iframe" element, so I am not sure how many components will iframeResizer function return.

Your suggestion is definitely something to look out for.

arminbaljic avatar Nov 30 '17 14:11 arminbaljic

If you only have one iFrame, it will return a one element array

davidjbradshaw avatar Nov 30 '17 14:11 davidjbradshaw

@davidjbradshaw if I'm not mistaken we will only want the first one [0] reference since it will be applied once for every iframe using this directive, it isn't it?

I have a couple of iframes using the directive:

<section class="admin-box">
  <iframe [src]="boxOfficeUrl" frameborder="0" width="100%" height="750" iframe-resizer></iframe>
  <iframe [src]="boxOfficeUrl" frameborder="0" width="100%" height="750" iframe-resizer></iframe>
</section>

So the will resize independently:

image

alfupe avatar Nov 30 '17 14:11 alfupe

@alfupe of yes I see it is passing this.element.nativeElement to iframeResize(), so in that case it will only get one element back. It is possible to pass looser selectors, rather than object references, in which case you can select more than one iFrame at a time.

So basically, just ignore me :)

davidjbradshaw avatar Nov 30 '17 14:11 davidjbradshaw

How, when you use the directive in the iframe, is the processedDocument() method called that @arminbaljic references in his MessagePreviewComponent?

To clarify,

I have a component, on which i've placed 3 iframes in its html. I've got the directive setup and the attribute added to the iframe.

The MessagePreviewComponent (referenced above) confuses me, because my iframe loads in the html of my component, and the MessagePreviewComponent seems to be processing the html source of the iframe as a string and concat'ing the script tag.

I'm not sure how i'm supposed to hook that processedDocument method.

paqogomez avatar Dec 01 '17 20:12 paqogomez

@paqogomez : Simplified structure of my use case is this:

<div class="thread-container">
    <app-thread> ... </app-thread>
    <app-thread> ... </app-thread>
    <app-thread> ... </app-thread>
    <app-thread>
        <!-- Some elements -->

        <!-- MessagePreviewComponent -->
        <app-message-preview [document]="emailContent">            
            <!-- BEGIN: This is located in message-preview.component.html -->
            <iframe appIframeResizer 
                    class="thread-content" 
                    [srcdoc]="processedDocument" 
                    title="Thread Content" 
                    scrolling="no" 
                    frameborder="0">
            </iframe>
            <!-- END: This is located in message-preview.component.html -->
        </app-message-preview>
    </app-thread>
    <app-thread> ... </app-thread>
    <app-thread> ... </app-thread>
</div>

As you can see, the iframe is inside of message preview component. The task of message preview component is to take "emailContent" passed as a document, do some processing with that and append tag that will fetch "iframeResizer.contentWindow.js". "iframeResizer.contentWindow.js" is needed inside iframe content in order for resizing to work (library requirements). From docs:

IFrame not resizing The most common cause of this is not placing the iframeResizer.contentWindow.min.js script inside the iFramed page. If the other page is on a domain outside your control and you can not add JavaScript to that page, then now is the time to give up all hope of ever getting the iFrame to size to the content. As it is impossible to work out the size of the contained page, without using JavaScript on both the parent and child pages.

MessagePreviewComponent does not run in iframe because iframe content is executed in separated context than the main window so it has no idea about Angular.

arminbaljic avatar Dec 02 '17 15:12 arminbaljic

@arminbaljic - MessagePreviewComponent is given document of type string through input decorator. In my case I don't have document coming from same domain. Is there a way in Angular to convert a document into string by passing url?

chaitanya1248 avatar Jan 11 '18 20:01 chaitanya1248

@chaitanya1248 Implement angular service that will get page content using HttpClient. If page content is loaded from another domain you will probably get No 'Access-Control-Allow-Origin' header is present on the requested resource. error. If you are not familiar with this error you can read about it here: Access Control in Angular or CORS. Check StackOverflow for any issue with CORS, there are many more responses.

The second option is to implement your own endpoint that will do the job for you. There are probably many tutorials on this topic. When you get response page, content in string form is available as someNode.outerHTML.

Additionally, please notice that method bypassSecurityTrustHtml is a security risk. Use this method only if you are 100% sure that content of the iframe will not contain any harmful scripts.

arminbaljic avatar Jan 12 '18 09:01 arminbaljic

@arminbaljic - Thank you for the response. Ill try the options available.

chaitanya1248 avatar Jan 12 '18 14:01 chaitanya1248

@arminbaljic Is there any chance you've released your Angular wrapper on GitHub or through NPM at all? That is super useful and solved the problem that my team and I had at work.

willsteinmetz avatar Jun 11 '18 16:06 willsteinmetz

@arminbaljic , How can i use the resize function here .

I have implemented this , but i have a problem .

When i am resizing the window screen to the minimize , the height of the iframe increases, but when i try to maximize , the height of iframe doesn't get decreased .

What can i do to achieve that ?

bhawin-boom avatar Aug 01 '18 09:08 bhawin-boom

@arminbaljic , i created a directive that will apply iframe-resizer, and a simpler version of "MessagePreviewComponent" but it shows me on page - null. How can i fixed it? In my app i have 4 different

Eduard17Kh avatar Nov 27 '18 07:11 Eduard17Kh

Does anybody import iframeResizerContentWindow in create-react-app (TypeScript) project? Is this way of importing valid? Why there is no type-definition for iframeResizerContentWindow?

Screen_Shot_2019-03-29_at_13_17_16

dmitrykrylov avatar Mar 29 '19 10:03 dmitrykrylov

FYI the NPM-types are broken in angular atm. see: #31020

wiegell avatar Jul 07 '19 16:07 wiegell

@arminbaljic Sorry to bring this up long after your answer, but I'm unsure where to include your MessagePreviewComponent so that it injects the script on the iFrame. Will this work with a cross-domain iframe?

Danyx1980 avatar Aug 12 '19 14:08 Danyx1980

Hi everyone. Actually tried all suggested methods with absolute failure, but found a way that seems to be somewhat easier as it's more direct in the way iFrameResize is called.

First you'll need a scriptLoadingService, to take teh js files and inject them dynamically in the code. Be sure to inject it on the app.module file asa provider, otherwise you'll be using a new instance of this service everytime, missing the loadedLibaries "cache" (to avoid loading a library again and again):

import { Injectable, Inject } from '@angular/core';
import { ReplaySubject, Observable, forkJoin } from 'rxjs';
import { DOCUMENT } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class RemoteLibraryService {
  // Ref.: https://codeburst.io/lazy-loading-external-javascript-libraries-in-angular-3d86ada54ec7

  private _loadedLibraries: { [url: string]: ReplaySubject<any> } = {};

  constructor(@Inject(DOCUMENT) private readonly document: any) {}

  lazyLoadJsCss(scriptUrl: string, stylesUrl: string): Observable<any> {
    return forkJoin([
      this.loadScript(scriptUrl),
      this.loadStyle(stylesUrl)
    ]);
  }

  loadScript(url: string): Observable<any> {
    if (this._loadedLibraries[url]) {
      return this._loadedLibraries[url].asObservable();
    }

    this._loadedLibraries[url] = new ReplaySubject();

    const script = this.document.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.src = url;
    script.onload = () => {
      console.log(`Script ${url} loaded...`);
      this._loadedLibraries[url].next();
      this._loadedLibraries[url].complete();
    };

    this.document.body.appendChild(script);

    return this._loadedLibraries[url].asObservable();
  }

  loadStyle(url: string): Observable<any> {
    if (this._loadedLibraries[url]) {
      return this._loadedLibraries[url].asObservable();
    }

    this._loadedLibraries[url] = new ReplaySubject();

    const style = this.document.createElement('link');
    style.type = 'text/css';
    style.href = url;
    style.rel = 'stylesheet';
    style.onload = () => {
      console.log(`CSS styles ${url} loaded...`);
      this._loadedLibraries[url].next();
      this._loadedLibraries[url].complete();
    };

    const head = document.getElementsByTagName('head')[0];
    head.appendChild(style);

    return this._loadedLibraries[url].asObservable();
  }
}

Then copy iframeResizer.js and iframeResizer.contentWindow.js files to your assets folder, lets say assets/js/

Finally, make an iframe wrapper component like this one:

SCSS file:

:host  {

  iframe {
    width: 1px;
    min-width: 100vw;
  }

}

Component file:

import { RemoteLibraryService } from './../services/remote-library.service';
import { Component, OnInit, AfterViewInit, OnDestroy, ElementRef, Renderer2 } from '@angular/core';

declare global {
  var iFrameResize: any;
}

@Component({
  selector: 'app-iframe-wrapper',
  templateUrl: './iframe-wrapper.component.html',
  styleUrls: ['./iframe-wrapper.component.scss']
})
export class IframeWrapperComponent implements OnInit, AfterViewInit, OnDestroy {

  iframeTag: string;
  hideButton: boolean;

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    public remoteLibraryService: RemoteLibraryService) {}

  ngOnInit() {
    // You can improve the code and  get the iframe url using an @Input()
    this.iframeTag     = `
      <iframe src="https://iframe-url" frameborder="0" scrolling="no" allowtransparency="true"></iframe>
      `;
  }

  ngAfterViewInit(): void {
    const scriptArray: Promise<any>[] = [
      this.remoteLibraryService.loadScript('/assets/iframeResizer.js').toPromise(),
      this.remoteLibraryService.loadScript('/assets/iframeResizer.contentWindow.js').toPromise(),
      // you can use the remoteLibraryService to load any other script required or even css files
    ];
    Promise.all(scriptArray).then(_ => {
      console.log('All scripts loaded');
      iFrameResize({
        log: true,
        checkOrigin: false,
        sizeWidth: true,
        minWidth: 100,
        heightCalculationMethod: 'lowestElement',
       // ... any other iframeResizer parameters 
      }, 'iframe');

      setTimeout(() => {
        // For some strange reason the iframe was stuck on the mobile sized view so I added this extra
        // instructions to adjust the size
        const iframe = this.el.nativeElement.querySelector('iframe');
        this.renderer.setStyle(iframe, 'width', '98vw');
        this.renderer.setAttribute(iframe, 'width', '100%');
      }, 1500);
    });
  }

  ngOnDestroy() {
    try {
      const iframe  = this.el.nativeElement.querySelector('iframe');
      iframe.iframeResizer.close();
    } catch(ifErr) {
      console.warn('Couldn't close the iframe');
    }
    this.iframeTag = null;
  }

  // Had some problems with the integration that was provided so I added a method to 
  // make the iframe full height. Just an emergency exit in case of ... emergencies... :P
  forceAdjustHeight() {
    const div = this.el.nativeElement.querySelector('div');
    const iframe = this.el.nativeElement.querySelector('iframe');
    this.renderer.addClass(div, 'original');
    this.renderer.removeStyle(iframe, 'height');
    this.renderer.setStyle(iframe, 'width', '100vw !important');
    this.renderer.setStyle(iframe, 'height', '2150px !important');
    this.renderer.setAttribute(iframe, 'width', '100%');
    this.renderer.setAttribute(iframe, 'height', '2150px');

    this.hideButton = true;
  }

}

And the component template:

<div #iframeWrapper [innerHTML]="iframeTag | safe: 'html'"></div>

<button
  *ngIf="!hideButton"
  (click)="forceAdjustHeight()" class="btn btn-sm btn-outline-dark text-light">Adjust size</button>

Also using a SafePipe, so HTML tags can be added without any problems:

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl } from '@angular/platform-browser';

@Pipe({
  name: 'safe'
})
export class SafePipe implements PipeTransform {

  constructor(private sanitizer: DomSanitizer) { }
  transform(value: string, type: string = 'html'): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
    switch (type) {
      case 'html': return this.sanitizer.bypassSecurityTrustHtml(value);
      case 'style': return this.sanitizer.bypassSecurityTrustStyle(value);
      case 'script': return this.sanitizer.bypassSecurityTrustScript(value);
      case 'url': return this.sanitizer.bypassSecurityTrustUrl(value);
      case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value);
      default: throw new Error(`Invalid safe type specified: ${type}`);
  }
  }

}

Thinking seriously on making a wrapper component. Hope it helps. Best regards from Chile.

jsanta avatar Dec 28 '20 20:12 jsanta

@davidjbradshaw @arminbaljic I followed all your instruction but it's not working. Is this plugin working on Angular 9 ?

Elka74100 avatar Jan 11 '21 13:01 Elka74100

@Elka74100 I have no idea, never used Angular, if you find that you need to do something new in version 9, can you please update this thread.

davidjbradshaw avatar Jan 12 '21 22:01 davidjbradshaw

@jsanta solution is the only one which worked fine for me. Though, when adding iframeResizer.js and iframeResizer.contentWindow.js don't forget to add iframeResizer.contentWindow.map and iframeResizer.map as well or it won't work properly.

Elka74100 avatar Feb 10 '21 15:02 Elka74100

@arminbaljic What is the 'document' passed into the 'processedDocument' fucntion?

vuongquang avatar Apr 28 '21 03:04 vuongquang

I just wanted to mention this here in case anyone else is having problems getting the iframeResizerContentWindow working with an Angular application embedded as an iframe in a non-Angular web site.

To solve this on my end, after the NPM install, I simply added the following to the polyfills.ts file on my Angular 12 project:

import 'iframe-resizer/js/iframeResizer.contentWindow.js';

That was all I needed and everything started working. No other changes needed to my Angular project.

clinicaloffice avatar May 19 '21 19:05 clinicaloffice

@arminbaljic @clinicaloffice I am trying to use iframe-resizer in angualr 10 for cross domain sites where the iframe is getting added by test.js using create element and setting dynamic src, but as per angular implementation we need to add the directive and add selector to iframe tag but here I am adding that tag by using loader file not hardcoded so how could I add that directive in case of dynamically created iframe tags, or is there any other way to implement it.

deeppatidar avatar Sep 20 '21 18:09 deeppatidar

follow up to @clinicaloffice I use iframeResizerContentWindow just by adding line in angular.json file

"architect": {
    "build": {
             ...
            "scripts": [
                  "node_modules/iframe-resizer/js/iframeResizer.contentWindow.js"
             ]

...

spdi avatar Nov 30 '21 14:11 spdi

I just wanted to mention this here in case anyone else is having problems getting the iframeResizerContentWindow working with an Angular application embedded as an iframe in a non-Angular web site.

To solve this on my end, after the NPM install, I simply added the following to the polyfills.ts file on my Angular 12 project:

import 'iframe-resizer/js/iframeResizer.contentWindow.js';

That was all I needed and everything started working. No other changes needed to my Angular project. Hi, do you mean without creating a directive and tag, just adding will line in the polyfils.ts, will make this work or do you mean in addition to creating a directive and tag , i need to follow the steps you have described. Thanks.

rmahmood19 avatar Jan 24 '22 00:01 rmahmood19

I just wanted to mention this here in case anyone else is having problems getting the iframeResizerContentWindow working with an Angular application embedded as an iframe in a non-Angular web site. To solve this on my end, after the NPM install, I simply added the following to the polyfills.ts file on my Angular 12 project: import 'iframe-resizer/js/iframeResizer.contentWindow.js'; That was all I needed and everything started working. No other changes needed to my Angular project. Hi, do you mean without creating a directive and tag, just adding will line in the polyfils.ts, will make this work or do you mean in addition to creating a directive and tag , i need to follow the steps you have described. Thanks.

rmahmood19: You still need to have the iFrameResizer code in your parent page (the one with the iFrame in it), but for the Angular application that is being run inside the iFrame I only needed to import the library with NPM and add the import statement to my polyfills.ts file.

clinicaloffice avatar Jan 24 '22 13:01 clinicaloffice