我想动态加载一个JavaScript库,比如说D3.js,使用一个类似于以下的Angular服务:
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;
}
它运行得很好,除非我用jQuery加载Angular,而不是默认情况下让它使用内置的jqLite。据我所知,当Angular使用jQuery时,onload
回调根本不会被调用。当Angular只是使用jqLite时,它肯定会被调用。
这里有一个使用jQuery的不工作的fiddle,还有一个没有jQuery的工作fiddle。请注意,除了是否加载jQuery之外,这些都是相同的。
因此,一个由两部分组成的问题:
为什么使用jQuery会导致这种行为差异
如何纠正或解决jQuery行为
至于为什么,我的猜测是,jqLite以某种方式配置了摘要循环,而Angular没有(或者不能?)为jQuery配置。如果这个猜测是准确的,那么这种行为似乎是不一致的,奇怪到了成为bug的地步(也就是说,在Angular中)。
至于如何,我认为(但尚未测试)我可以在Angular之后加载jQuery,并在需要时进行一些丑陋的解包/重写以"转换"jqLite对象,使其拥有jQuery的完整API。如果存在更干净的方法,我更喜欢这种方法。
这不是Angular中的bug。。。之所以会发生这种情况,是因为jQuery.append以一种特殊的方式处理脚本元素,基本上是通过jQueryAjax单独加载脚本元素上的任何src
URL。然后在window
上而不是在脚本元素上激发load
。jqLite.append没有模仿jQuery.append.的这一方面
解决方案是使用DOM而不是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;
}
通过此更改,jqLite Angular和jQuery Angular之间的行为是一致的。