将D3注入AngualrJS的适当公约
我见过使用全局D3对象的指令,我也看到了通过将其返回到服务中来注入全局D3对象的指令,并且我看到了指令添加了D3脚本并返回了一个承诺,即解决了提供D3对象的脚本加载问题。
在注射服务中使用它似乎最有意义(请参见示例1和2),但我不确定哪种方式更好。 例2将保证D3在其上运行任何代码之前已经被加载,但似乎没有人这样做,再加上它意味着你必须将整个指令包装在服务器中,否则d3
和创建的svg
对象将不在范围或可能未定义(请参见示例2),但至少编译的承诺我相信会始终解决,请参阅示例3。
示例1:服务传递D3全局对象
.factory('D3Service', [,
function () {
// Declare locals or other D3.js
// specific configurations here.
return d3;
}]);
示例2:将D3脚本添加到DOM并传递承诺的服务
.factory('D3Service', ['$window', '$document', '$q', '$rootScope',
function ($window, $document, $q, $rootScope) {
var defer = $q.defer();
var scriptTag = $document[0].createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = 'https://d3js.org/d3.v3.min.js';
scriptTag.async = true;
scriptTag.onreadystatechange = function () {
if (this.readyState == 'complete') {
onScriptLoad();
}
}
scriptTag.onload = onScriptLoad;
var script = $document[0].getElementsByTagName('body')[0];
script.appendChild(scriptTag);
//---
// PUBLIC API
//---
return {
d3: function () {
return defer.promise;
}
};
//---
// PRIVATE METHODS.
//---
// Load D3 in the browser
function onScriptLoad () {
$rootScope.$apply(function () {
defer.resolve($window.d3);
});
}
}]);
示例3:使用编译添加SVG并不意味着SVG在Link中可用,但至少编译的promise始终会先解析
// Perform DOM and template manipulations
function compile ($element, $attrs, $transclude) {
var svg;
// Callback provides raw D3 object
D3Service.d3().then(function (d3) {
// Create a responsive SVG root element
svg = d3.select($element[0])
.append('svg')
.style('width', '100%');
});
// Return the link function
return function($scope, $element, $attrs) {
// Is svg undefined?
// Maybe? so have to wrap everything again in service
D3Service.d3().then(function (d3) {
function render() {
// d3 and svg guaranteed to be available, but code gets really ugly looking and untestable
}
});
function render() {
// d3 and svg have to be passed in as they may not be available, but code is cleaner
}
};
}
遇到d3
和Angular
问题时,我有类似的问题。 似乎有几种方法可以解决这个问题; 每个都是可行的,但没有一个感觉平稳或自然。 在其核心上, d3
和Angular
似乎是两种截然不同的技术,而且它们并不能很好地一起玩。 不要误解我的意思,他们很合作,但他们需要彼此热身。 所以充其量,我们可以在Angular
框架内为d3
一个操场。 我相信这个操场应该是一个directive
。
但关于返回promise的模块化d3Service
方法(每次加载d3.js
文件):
angular.module('myApp.directives', ['d3'])
.directive('barChart', ['d3Service', function(d3Service) {
return {
link: function(scope, element, attrs) {
d3Service.d3().then(function(d3) {
// d3 is the raw d3 object
});
}}
}]);
虽然在ngNewsletter中对此进行了详细的介绍,但使用一种将script
标记直接写入DOM的服务似乎有些矫枉过正,因为它可能只包含在index.html
并包含所有其他javascript文件。 我的意思是,我们有一个我们知道使用这个文件的directive
,为什么不只是有意加载呢? 没有必要像现在似的跳过篮球,只是:
<script src="/js/third-party/d3js/d3.min.js"></script>
然而,这种方法的承诺确实提供了模块化 - 假设我们正在构建多个应用程序,并且每个应用程序都需要d3
,那么是的,能够在应用程序级别非常容易地注入我们的d3
模块非常好。 但是,即使我们知道在初始加载后立即解决问题,您仍然必须等待该承诺,但仍需要解决该问题。 在使用它的任何指令或控制器中。 总是。 游民。
正如我所说,我选择在我的index.html
包含d3.js
,因此我可以在我的指令中访问它,而无需解决承诺。 下面是一个并行的例子:FWIW,我使用JQuery承诺支持Angular Promises,所以当我需要JQuery时我该怎么做? 嗯,我只是在我需要它时调用它( $.Deferred()
),我的观点是以类似的方式调用d3
对我来说似乎并不那么令人震惊。
虽然我确实使用了d3Service
,但它更多的是帮助函数而不是其他任何东西。 例如,当我想获得一个可以工作的SVG时,为什么不调用一个给我一个响应SVG的函数:
指令(链接)
var svg = d3Service.getResponsiveCanvas(scope.id, margin, height, width);
服务
app.service('d3Service', function() {
return {
getResponsiveCanvas: function(id, margin, height, width) {
return d3.select('#' + id)
.append('div')
.classed('svg-container', true)
.append('svg')
.attr('id', 'svg-' + id)
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox', '0 0 ' + (width + margin.left + margin.right) + ' ' + (height + margin.top + margin.bottom))
.classed('svg-content-responsive', true)
.append('g')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
}
}
});
我有类似的功能来添加轴到SVG。 这确实有代码的味道,但是再一次,就d3
本身而言,我们直接操纵DOM,所以我的经验就是放在这里的任何地方,它会变得丑陋,而且感觉不像Angular-like,所以你可能会以及制作一些让您的生活更轻松的服务。