请考虑以下指令:(Live Demo)
app.directive('spinner', function() {
return {
restrict: 'A',
scope: {
spinner: '=',
doIt: "&doIt"
},
link: function(scope, element, attrs) {
var spinnerButton = angular.element("<button class='btn disabled'><i class='icon-refresh icon-spin'></i> Doing...</button>");
element.after(spinnerButton);
scope.$watch('spinner', function(showSpinner) {
spinnerButton.toggle(showSpinner);
element.toggle(!showSpinner);
});
}
};
});
使用如下:
<button ng-click="doIt()" spinner="spinIt">Spin It</button>
如果spinner
的值(即此示例中$scope.spinIt
的值)为true
,则应隐藏该元素,而应显示spinnerButton
。如果spinner
的值为false
,则该元素应该可见,并且应隐藏spinnerButton
。
这里的问题是doIt()
不在隔离范围内,因此不会在点击时调用。
实施该指令的“Angular方式”是什么?
答案 0 :(得分:8)
我的建议是看看这些纺纱厂发生了什么。 Be a little more API focused。
相关部分如下。我们使用常规回调来指示我们何时完成,因此微调器知道重置按钮的状态。
function SpinDemoCtrl($scope, $timeout, $q) {
$scope.spinIt = false;
$scope.longCycle = function(complete) {
$timeout(function() {
complete();
}, 3000);
};
$scope.shortCycle = function(complete) {
$timeout(function() {
complete();
}, 1000);
};
}
app.directive('spinnerClick', function() {
return {
restrict: 'A',
scope: {
spinnerClick: "=",
},
link: function(scope, element, attrs) {
var spinnerButton = angular.element("<button class='btn disabled'><i class='icon-refresh icon-spin'></i> Doing...</button>").hide();
element.after(spinnerButton);
element.click(function() {
spinnerButton.show();
element.hide();
scope.spinnerClick(function() {
spinnerButton.hide();
element.show();
});
});
}
};
});
Here's one that expects use of $q
。使用Angular风格的异步操作可以更好地工作,并通过在实现承诺时重置微调器来消除回调函数。
答案 1 :(得分:2)
以下是我最终提出的指令的完美版本(基于Yuki的建议),以防万一:( CoffeeScript)
app.directive 'spinnerClick', ->
restrict: 'A'
link: (scope, element, attrs) ->
originalHTML = element.html()
spinnerHTML = "<i class='icon-refresh icon-spin'></i> #{attrs.spinnerText}"
element.click ->
return if element.is('.disabled')
element.html(spinnerHTML).addClass('disabled')
scope.$apply(attrs.spinnerClick).then ->
element.html(originalHTML).removeClass('disabled')
像这样使用它:
<button class="btn btn-primary" spinner-click="createNewTask()"
spinner-text="Creating...">
Create
</button>
控制器代码:
TasksNewCtrl = ($scope, $location, $q, Task) ->
$scope.createNewTask = ->
deferred = $q.defer()
Task.save $scope.task, ->
$location.path "/tasks"
, (error) ->
// Handle errors here and then:
deferred.resolve()
deferred.promise
答案 2 :(得分:1)
是的,它会在您的隔离范围内调用doIt。
在这种情况下你可以使用$ parent.doIt
<button ng-click="$parent.doIt()" spinner="spinIt">Spin It</button>
答案 3 :(得分:1)
来自AngularJS文档(http://docs.angularjs.org/guide/directive):
&安培;或&amp; attr - 提供在父作用域的上下文中执行表达式的方法。如果未指定attr名称,则假定属性名称与本地名称相同。范围的给定和窗口小部件定义:{localFn:'&amp; myAttr'},然后隔离范围属性localFn将指向count = count + value表达式的函数包装器。通常需要通过表达式将数据从隔离范围传递到父范围,这可以通过将局部变量名称和值的映射传递到表达式包装器fn来完成。例如,如果表达式是increment(amount),那么我们可以通过将localFn称为localFn({amount:22})来指定金额值。
所以在你的范围声明中包含doIt: "&doIt"
,然后你就可以在你的孤立范围内使用doIt作为函数。
答案 4 :(得分:0)
我很困惑为什么你没有在指令中打包所有内容,就像它是一个独立的模块一样。这至少是我要做的。换句话说,您在HTML中有click-handler,在指令中有一些行为,在外部控制器中有一些行为。这使您的代码更不便携,更分散。
无论如何,你可能有理由不共享,但我的建议是将所有“Spin It”相关内容放在spinner指令中。这意味着点击处理程序,doIt()
函数和模板内容都在链接函数中。
这样就无需担心共享范围和代码纠缠。或者,我只是错过了什么?
答案 5 :(得分:0)
我不知道'有角度'的做事方式,但我建议不要使用孤立的范围,而只是创建一个子范围。然后,您可以attrs.$observe
获取指令所需的任何属性。
即。 :
app.directive('spinner', function() {
return {
restrict: 'A',
scope: true, //Create child scope not isolated scope
link: function(scope, element, attrs) {
var spinnerButton = angular.element("<button class='btn disabled'><i class='icon-refresh icon-spin'></i> Doing...</button>");
element.after(spinnerButton);
//Using attrs.$observe
attrs.$observe('spinner', function(showSpinner) {
spinnerButton.toggle(showSpinner);
element.toggle(!showSpinner);
});
}
};
});
我发现这种方法比使用'$ parent'来逃避其他指令(例如ngClick或ngModel)中的隔离范围更好,因为指令的最终用户不需要知道使用你的指令是否需要它们在核心angularjs指令上使用'$ parent'或不使用。
答案 6 :(得分:0)
使用 CoffeeScript 和 FontAwesome 图标。
app.directive 'spinnerClick', ["$compile", ($compile) ->
restrict: 'A'
link: (scope, element, attrs) ->
originalHTML = element.html()
spinnerHTML = "<i class='fa fa-refresh fa-spin'></i> "
element.click ->
return if element.is('.disabled')
element.html(spinnerHTML + originalHTML).addClass('disabled')
$compile(element.contents())(scope)
scope.$apply(attrs.spinnerClick).finally ->
element.html(originalHTML).removeClass('disabled')
$compile(element.contents())(scope)
]