1

I've hooked up a lazy loader in Angular. It pulls in full templates and extracts key information from that full template in order to populate a partial. This full page template has script tags which load in and then register with the existing app. All of this works fine. My problem is that I'd like to remove the only use of jQuery in this approach.

The root issue is that the JS inside of something.js doesn't execute when using $element.html(), but it does execute when using $.html(), despite the script tag being placed in the DOM in both approaches.

Working code, including lazy loader and post-bootstrap registration of lazy-loaded JS:

$http.get("/path/to/file.html").success(function(response) {
  // response is a full HTML page including <doctype>
  var partial = getOnlyWhatWeNeed(response);

  // partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'

  // i'd like the following to not rely on full jQuery.
  $("#stage").html(partial);
  $("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});

I've tried what seems like the logical translation:

$element.html($compile(partial)($scope));

and the DOM is created properly, but the JS inside of the loaded <script> tag doesn't actually execute. My research suggested this was an $sce issue, so I tried:

$element.html($compile($sce.trustAsHtml(partial)($scope));

but i get the same result. the DOM is fine, but the JS doesn't actually execute and so I get undefined controller issues.

I've tried playing with $sce.JS and $sce.RESOURCE_URL but the docs didnt elaborate much so I'm not sure I know whether or not what I'm trying is even right.

I've also tried $element[0].innerHTML but I get the same result as $element.html().

Preemptive disclaimer: I can trust the incoming HTML/JS. I know it's inadvisable. This isn't my baby and it is much more complicated than I explained so please try to stay on topic so other people in this position may not have as hard of a time as I am :)

The $http.get happens in a provider, and the $element.html happens in a directive. I consolidated them to remove noise from the problem.

oooyaya
  • 1,773
  • 1
  • 15
  • 39
  • 1
    Can you simply extract the contents of the ` – Blazemonger Apr 29 '14 at 15:47
  • I could do a getElementsByTagName(script) and AJAX the SRC and eval it. I know eval() is evil so maybe there's more to do surrounding that. – oooyaya Apr 29 '14 at 16:32
  • Eval isn't evil if you trust the source. It's just a bad habit because new developers use it when it's not necessary. – Blazemonger Apr 29 '14 at 16:46
  • Blazemonger's solution is most similar to what I ended up implementing. There's a whole slew of other things that are going wrong, so I won't waste anyone's time by posting a broken solution :( – oooyaya May 30 '14 at 19:23
  • Those coming here and wondering why el.html() works but element.html() doesnt work - its because jQuery hands the creation of the DOM off to append(). jqLite doesnt do this. use element.append() instead. – oooyaya May 30 '14 at 19:25

1 Answers1

0

Jquery will find any script tags and evaluate them (either a direct eval or appending them to the head for linked scripts) when calling html(), see this answer. I'm assuming angular's jquery lite doesn't do this. You would need to effectively replicate what jquery is doing and look for script tags in the html you are appending.

Something like this (although I haven't tested it):

$http.get("/path/to/file.html").success(function(response) {
  // response is a full HTML page including <doctype>
  var partial = getOnlyWhatWeNeed(response);

  // partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'

  var d = document.createElement('div');
  d.innerHTML = partial;
  var scripts = d.getElementsByTagName('script');

  for (var i = 0; i < scripts.length; i++) {
      document.head.appendChild(scripts[0]);
  }

  $("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});

This is far from an ideal solution as it gives you no guarantee of when things are loaded and doesn't really handle dependencies across scripts. If you can control the templates it would be simpler to remove the scripts from them and load them independently.

Community
  • 1
  • 1
slashnick
  • 26,167
  • 10
  • 55
  • 67
  • ok so really I don't even need to print the script tags to the DOM. I can just grab the contents of them and it'll work from memory. – oooyaya Apr 29 '14 at 16:31