1

I'd like to dynamically load a JavaScript library, say for example D3.js, with an Angular service something like this one:

function factoryD3Loading ($q, $document, $rootScope, $window) {
    var d = $q.defer();
    var script = $document[0].createElement('script');
    script.type = 'text/javascript';
    script.async = 'async';
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js';
    script.onload = function () {
        $rootScope.$apply(function () {
            d.resolve($window.d3);
        });
    };
    $document.find('body').append(script);
    return d.promise;
}

It works great unless I load Angular with jQuery instead of letting it use its built-in jqLite by default. From what I can tell, the onload callback is simply never invoked when Angular is using jQuery. It's definitely invoked when Angular is just using jqLite.

Here's a non-working fiddle using jQuery, and here's a working fiddle without jQuery. Note that these are identical except for whether jQuery is loaded.

So, a two-part question:

  1. Why does usage of jQuery cause this difference in behavior?

  2. How can this with-jQuery behavior be corrected or worked around?

As for why, my guess is that jqLite is somehow configured with the digest cycle in a way that Angular doesn't (or can't?) configure for jQuery. If that guess is accurate, this behavior seems inconsistent and strange to the point of being a bug (in Angular, that is).

As for how, I think (but haven't yet tested) that I can load jQuery after Angular and do some ugly unwrap/rewrap to "convert" jqLite objects to have jQuery's full API when I need it. I'd prefer a cleaner approach, if one exists.

1

1 Answer 1

0

It's not a bug in Angular... It's happening because jQuery.append handles script elements in a special way, basically loading any src URL on a script element separately through jQuery Ajax. load is then fired on window, not on the script element. jqLite.append does not emulate this aspect of jQuery.append.

The solution is to append using the DOM instead of angular.element:

function factoryD3Loading ($q, $document, $rootScope, $window) {
    var d = $q.defer();
    var script = $document[0].createElement('script');
    script.type = 'text/javascript';
    script.async = 'async';
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js';
    script.onload = function () {
        $rootScope.$apply(function () {
            d.resolve($window.d3);
        });
    };
    $document[0].body.appendChild(script); // DOM, not angular.element
    return d.promise;
}

With this change, behavior is consistent between jqLite-Angular and jQuery-Angular.

Sign up to request clarification or add additional context in comments.

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.