14

I have load the HTML page by http.get() method, and i add content this page to div tag.

getRequestToAssignPage (param: string) : any {

    return this.$http.get(param)
        .map((res: Response) => {

            this.status = res;

            return res.text();
        })
        .toPromise()
        .then(response => {



            let restr: string = response;



            restr = restr.replace(/(<head[^>]*)(?:[^])*?\/head>/ig, '')
                .replace(/(<(\/?)body([^>]*?)>)/g, '')
                .replace(/(<style[^>]*)(?:[^])*?\/style>/g, '')
                .replace(/(<(\/?)html([^>]*?)>)/g, '')
                .replace(/(<app-root[^>]*)(?:[^])*?\/app-root>/ig, '')
                .replace(/(<\?[\s\S]*?\?>)|(<!DOCTYPE\s+\w+\s+\[[\s\S]*?\]>)|(<!\w[\s\S]*?>)/g, '')
                .replace(/(href\s*=\s*(?:"))/ig, 'href="/#')
                .replace(/(href\s*=\s*(?:'))/ig, "href='/#");



            this.response = restr;


        })
        .catch(error => this.status = error );

}

How you do you see, this method, put response in variable, and parse string by regular expressions Ok, and next I add it to div, like this

<div [innerHTML]="response | safe"></div>

Good, my page is display. But, scripts doesn't work. They are exist in the div tag, but doesn't execute.

I had tried do that with eval() but this finished by poorly

let scripts: string[] = restr.match(/\<scr[\s\S]*?ipt>/g);

            this.srcfield.nativeElement.innerHTML = '';

            scripts.forEach((value, index) => {
                eval.call(null, (this.srcfield.nativeElement.innerHTML = value));
            });

SyntaxError: Unexpected token <

Why innerHTML doesn't execute loaded script tags? How i can fix that?

3 Answers 3

27

Based on the Adam's solution you can implement a custom directive along with the pipe and re-insert scripts into the DOM. This would account for both cases: inline scripts and "src" scripts. Keep in mind that allowing scripts like so is very dangerous.

Pipe:

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({ name: 'safeHtml' })
export class SafeHtmlPipe implements PipeTransform {
    constructor(private sanitizer: DomSanitizer) { }

    transform(html) {
        return this.sanitizer.bypassSecurityTrustHtml(html);
    }
}

Directive:

import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({ selector: '[runScripts]' })
export class RunScriptsDirective implements OnInit {
    constructor(private elementRef: ElementRef) { }
    ngOnInit(): void {
        setTimeout(() => { // wait for DOM rendering
            this.reinsertScripts();
        });
    }
    reinsertScripts(): void {
        const scripts = <HTMLScriptElement[]>this.elementRef.nativeElement.getElementsByTagName('script');
        const scriptsInitialLength = scripts.length;
        for (let i = 0; i < scriptsInitialLength; i++) {
            const script = scripts[i];
            const scriptCopy = <HTMLScriptElement>document.createElement('script');
            scriptCopy.type = script.type ? script.type : 'text/javascript';
            if (script.innerHTML) {
                scriptCopy.innerHTML = script.innerHTML;
            } else if (script.src) {
                scriptCopy.src = script.src;
            }
            scriptCopy.async = false;
            script.parentNode.replaceChild(scriptCopy, script);
        }
    }
}

Usage:

<div [innerHTML]="myDynamicMarkup | safeHtml" runScripts></div>
Sign up to request clarification or add additional context in comments.

4 Comments

this would be exactly what I needed, but my script uses document.write and I guess it won't work when executed this way and I can't change the script..
In my case the runScript directive runs before data gets loaded to [innerHtml] thus scripts are not extracted. Any idea?
@WalterLuszczyk I know your question was asked long ago, but in case anyone else has the same problem, I would offer 2 suggestions. First, you could always adjust the number of miliseconds in 'setTimeout()'. But the better solution would be to put an *ngIf on your element so it only renders when the data is present. That should force the directive to run after the data has already loaded.
can you explain why runScripts is needed while the safeHtml pipe is used and the script tag is correctly rendered in the page ? What prevent the script from being executed ?
11

In your template, you can set up something like:

<div #htmlContainer [innerHTML]="trustedHTML"></div>

In the corresponding component, you can load your HTML into your template, build up an array of script tags, and eval each one:

@ViewChild('htmlContainer') container;

ngOnInit() {
  this.http.get('html-file.html').subscribe((res: Response) => {
    this.trustedHTML = this.sanitizer.bypassSecurityTrustHtml(res.text());

    setTimeout(() => { //wait for DOM rendering
      let scripts = this.container.nativeElement.getElementsByTagName('script');
      for (let script of scripts){
        eval(script.text)
      }
    })

  });

I hate that I have to resort to using setTimeout and eval for something so seemingly simple, but it works.

...but not for <script src=".... I ended up importing jQuery and using $(parent).load(url);. If someone has a pure Angular solution, please post it.

Comments

0

When the scripts are dependents on the each other, e.g. a embed code, which runs after the library is loaded. following is the improved version of RunScriptDirective

import { Directive, ElementRef, OnInit } from '@angular/core';

@Directive({ selector: '[runScripts]' })
export class RunScriptsDirective implements OnInit {
    constructor(private elementRef: ElementRef) { }
    ngOnInit(): void {
        setTimeout(() => { // wait for DOM rendering
            const scripts = <HTMLScriptElement[]>this.elementRef.nativeElement.getElementsByTagName('script');
            this.at=0;
            this.total = scripts.length;
            this.reinsertScripts(scripts);
        });
    }
    at: number = 0;
    total: number;
    reinsertScripts(items: HTMLScriptElement[]): void {
        const script = items[this.at];
        if (script) {
            const scriptCopy = <HTMLScriptElement>document.createElement('script');
            scriptCopy.type = script.type ? script.type : 'text/javascript';
            if (script.innerHTML) {
                scriptCopy.innerHTML = script.innerHTML;
            } else if (script.src) {
                scriptCopy.src = script.src;
            }
            scriptCopy.async = false;
            scriptCopy.onload = (e) => {
                this.at++;
                this.reinsertScripts(items);
            }
            script.parentNode.replaceChild(scriptCopy, script);
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.