davidjbradshaw / iframe-resizer

Keep iFrames sized to their content.
https://iframe-resizer.com
Other
6.7k stars 982 forks source link

Guidelines for consuming this library in angular 2 #478

Closed adaneam closed 3 months ago

adaneam commented 7 years ago

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

davidjbradshaw commented 7 years ago

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.

lucasnichele commented 7 years ago

I need the same. Please help us.

arminbaljic commented 7 years ago

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.

gausie commented 7 years ago

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

davidjbradshaw commented 7 years ago

@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.

gausie commented 7 years ago

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
davidjbradshaw commented 7 years ago

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

davidjbradshaw commented 7 years ago

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

arminbaljic commented 7 years ago

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 commented 7 years ago

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

alfupe commented 7 years ago

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

lasimone commented 7 years ago

+1

zvaryuga commented 7 years ago

+2

arminbaljic commented 7 years ago

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 commented 7 years ago

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.

davidjbradshaw commented 7 years ago

@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

arminbaljic commented 7 years ago

@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.

davidjbradshaw commented 7 years ago

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

alfupe commented 7 years ago

@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

davidjbradshaw commented 7 years ago

@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 :)

paqogomez commented 6 years ago

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.

arminbaljic commented 6 years ago

@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.

chaitanya1248 commented 6 years ago

@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?

arminbaljic commented 6 years ago

@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.

chaitanya1248 commented 6 years ago

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

willsteinmetz commented 6 years ago

@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.

bhawin-boom commented 6 years ago

@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 ?

Eduard17Kh commented 6 years ago

@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