将 D3 注入 AngularJS 的正确约定
Posted
技术标签:
【中文标题】将 D3 注入 AngularJS 的正确约定【英文标题】:Proper Convention for Injecting D3 into AngualrJS 【发布时间】:2016-03-04 18:48:56 【问题描述】:我看到了只使用全局 D3 对象的指令,我还看到了通过在服务中返回全局 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:使用 Compile 添加 SVG 并不意味着 SVG 在 Link 中可用,但至少 compile 的 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.js 除了将d3
放在窗口范围内之外,没有什么比 $interval
承诺检查它是否最终存在更优雅的了。 [在 IE8 上,无论如何]
嗨@Brian,所以在每个图形指令的link
中等待$interval 承诺,然后使用D3 的全局引用?似乎解决方案可以/应该是 DRYer,但也许不是......
我会实现“如果 d3 未定义,则加载 d3 并等待,否则立即解决”。如果有人对包括IE8在内的浏览器有更好的解决方案,请@我!
我要么使用全局方法,要么使用第一种方法。我只看到尝试将其注入 DOM 的方法带来的痛苦。为什么要打扰?
【参考方案1】:
在遇到d3
和Angular
的问题时,我也有类似的问题。似乎有几种方法可以解决这个问题;每个都是可行的,但没有一个感觉光滑或自然。从本质上讲,d3
和Angular
似乎是两种截然不同的技术,它们在开箱即用时不能很好地结合使用。不要误会我的意思,他们一起工作非常出色,但他们需要彼此热身。所以充其量,我们可以在Angular
框架内给d3
一个操场。我相信这个游乐场应该是directive
。
但是关于返回承诺的模块化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 的服务似乎有点过头了,因为它可以与所有其他 javascript 文件一起包含在index.html
中.我的意思是,我们有一个directive
,我们知道它使用了这个文件,那么为什么不故意加载它呢?似乎不需要跳过箍,只是:
<script src="/js/third-party/d3js/d3.min.js"></script>
但是,这种方法确实提供了模块化——假设我们正在构建多个应用程序并且每个应用程序都需要d3
,那么是的,能够非常轻松地在应用程序级别注入我们的d3
模块非常棒。但是,您总是必须等待该承诺,即使我们知道它会在初始加载后立即解决,但您仍然需要解决它。在任何使用它的指令或控制器中。总是。无赖。
正如我所说,我选择在我的index.html
中包含d3.js
,因此我可以在我的指令中访问它而无需解决承诺。这可能是一个并行:FWIW,我使用 JQuery 承诺而不是 Angular 承诺,那么当我需要 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,所以您不妨制作一些让您的生活更轻松的服务。
【讨论】:
哇,谢谢,这很有帮助。在将它添加到 index.html 并在指令中访问它时,您是否遇到过 d3 未定义的问题?似乎您必须添加一个 init() 方法,如 cmets 中建议的 @Brian,它在加载每个单独的图表之前运行 $interval 检查 $window.d3。从两个角度欣赏答案。 @mtpultz 因此,通过在我们的 HTML 文件中直接包含d3
,我们保证它会在 Angular 初始化时被加载并准备好使用。因此,我没有遇到 d3 未定义的情况。可以这样想,只要包含在我们的 HTML 中,它就只是另一个由浏览器加载并提供给 JS 引擎的 JS 文件。如果你使用 underscore.js 或 jquery,或任何其他第三方 JS 库,它是相同的流程。 @Brian 似乎正在引用承诺的加载,如果未正确加载,则可能未定义。但我注意到的方法不使用承诺
好吧,我总是忘记脚本是如何加载的。因此,这是基于它们按顺序加载的,只要您不将它们设置为异步即可。谢谢:)以上是关于将 D3 注入 AngularJS 的正确约定的主要内容,如果未能解决你的问题,请参考以下文章
_servicename_ 中的下划线在 AngularJS 测试中是啥意思?