Search code examples
javascriptangulardynamic-html

How to execute dynamic HTML & JavaScript in Angular?


The problem is like this. There is an API that generates PDFs using HTML to PDF mechanism. The API returns the nicely generated PDFs to an Angular app. So far so good. Now there is a new requirement that pieces from these PDFs should be embedded into some Angular components. I get these pieces from the API as strings and now I must find a way to make them run. The problem is that some have JavaScript in them and this code is not executed by the Angular when I bind them to the components. Any advice or workaround is welcome. I have created an example with such code for a easy testing and understanding of the problem.

https://stackblitz.com/edit/angular-fgcvb2?file=src%2Fapp%2Fapp.component.ts


Solution

  • TLDR: Check this fixed working example of the code you provided. This one uses your current approach, so it executes the script tag manually.


    The answer is that there's no pretty way of doing what you want, as briefly stated in specs 4.3.1, 8.4 and 8.2.3.5 of the HTML Standard, when parsing <script>, the scripts won't be executed, so you'll have to either do it manually, or renderize your script as a source page:

    Changing the src, type, charset, async, and defer attributes dynamically has no direct effect; these attribute are only used at specific times described below.

    The scripting flag is set to "enabled" if scripting was enabled for the Document with which the parser is associated when the parser was created, and "disabled" otherwise.

    The scripting flag can be enabled even when the parser was originally created for the HTML fragment parsing algorithm, even though script elements don't execute in that case.


    1. Execute scripts manually (implemented here)

    For your current approach, as it is to call the API's as a service, and then adding the HTML and expecting the JS to be executed. Unfortunately, there's no pretty way of doing this. Similar questions had been asked several times in SO (answer1, answer2, answer3, answer4, etc.). Most of them end up doing something like this:

    // (1) to load js files
    var scripts = document.getElementsByTagName('script');
    Array.from(scripts).forEach(function(node) {
      var parent=node.parentElement
      var script = document.createElement('script');
      script.src = node.src;
      parent.insertBefore(script, node);
      parent.removeChild(node);
    });
    // (2) to execute directly js
    var scripts = document.getElementsByTagName('script');
    Array.from(scripts).forEach(function(script) {
        eval(script.innerHTML);
    });
    

    As caveant, this solution may end up in other issues (like unsafe-eval, and more).


    2. Load your API inside an IFRAME

    Best way would be as suggested in comments, to load your whole api response as an individual HTML page inside an IFRAME. Something similar to this:

    <iframe id="pdf1"></iframe>
    <script> 
        document.getElementById('pdf1').src = '../path/to/api/which/returns/the/html-with-js-here?parameter1=value1&parameter2=value2&etc=values';
    </script>
    

    I personally recommend this approach as it is the more natural way. Also, you have something all those other question/answer don't have; you have an API dedicated to retrieve your HTML with JS. So just, instead of calling in it as an API, navigate to it.