考虑以下spinner-click
指令:
指令使用:
<button class="btn btn-mini"
ng-class="{'btn-warning': person.active, disabled: !person.active}"
spinner-click="deleteItem($index)"
spinner-text="Please wait..."
spinner-errors="alerts">
Delete
</button>
指令:
app.directive('spinnerClick', function() {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
var originalHTML = element.html();
var spinnerHTML = "<i class='icon-refresh icon-spin'></i> " + attrs.spinnerText;
element.click(function() {
if (element.is('.disabled')) {
return;
}
element.html(spinnerHTML).addClass('disabled');
scope.$apply(attrs.spinnerClick).then(function() {
element.html(originalHTML).removeClass('disabled');
}, function(errors) {
element.html(originalHTML).removeClass('disabled');
// This is ugly! Is there a better way?
var e = scope[attrs.spinnerErrors];
e.length = 0;
e.push.apply(e, errors);
});
});
}
};
});
控制器:
app.controller('MainCtrl', function($scope, $q, $timeout) {
$scope.alerts = ['First alert'];
$scope.people = [
{ name: 'David', active: true },
{ name: 'Layla', active: false }
];
$scope.deleteItem = function(index) {
var defer = $q.defer();
$timeout(function() {
defer.reject(["Something 'bad' happened.", "Check your logs."]);
}, 2000);
return defer.promise;
};
});
注意: spinner-click
可与其他指令一起使用(例如,此示例中为ng-class
)。
如您所见,我使用非常讨厌的方式在指令中设置$scope.alerts
。你能找到更好的方法吗?
更新: (DEMO)
我试图像这样使用$parse
:
var errorsModel = $parse(attrs.spinnerErrors);
errorsModel.assign(scope, errors);
这不起作用。
但是,如果我有spinner-errors="wrapper.alerts"
而不是spinner-errors="alerts"
,it does work!
有没有办法避免使用包装器?
答案 0 :(得分:3)
我认为你可以更简单地使用隔离范围。
而不是scope: true,
,你应该把:
scope:{
spinnerClick:"&",
spinnerText : "@",
spinnerErrors: "="
}
然后,在您的指令中直接使用scope.spinnerClick
,scope.spinnerText
,scope.spinnerErrors
。
&amp; 用于绑定属性中定义的函数表达式并将其传递给指令的作用域, @ 将绑定属性的文本值和 = 将设置与属性中传递的表达式的双重绑定。
你可以在这里做一个更精确的解释http://docs.angularjs.org/guide/directive(查看长版本),以及更清晰的解释http://www.egghead.io/(查看隔离范围视频,只需几分钟时间让它看起来如此简单)。
答案 1 :(得分:2)
回答你关于丑陋的原始问题
// This is ugly! Is there a better way?
var e = scope[attrs.spinnerErrors];
e.length = 0;
e.push.apply(e, errors);
您可以使用angular.copy
来获得相同的结果
angular.copy(errors, scope[attrs.spinnerErrors]);
这在你的指令中如此丑陋的原因是由于你使用了一个子范围。如果您没有创建子范围,或者愿意创建隔离范围,那么这不是问题。您无法使用$scope.alerts
,因为
子范围获取自己的属性,隐藏/隐藏父级 同名财产。你的解决方法是
- 在模型的父级中定义对象,然后在子级中引用该对象的属性:parentObj.someProp
- 使用$ parent.parentScopeProperty(并非总是可行,但在可能的情况下比1.更容易)
- 在父作用域上定义一个函数,并从子作业中调用它(并非总是可行)
醇>
可以找到详细的解释here。
答案 2 :(得分:0)
一个选项是在控制器中创建一个可以在指令中调用的setter函数。然后可以使用对子作用域中的函数的引用来设置父作用域中的值。另一个选择是创建隔离范围,然后使用&
绑定传递setter函数。
答案 3 :(得分:0)
你用$ parse有了正确的想法。问题是您将新的错误数组分配给子作用域,该作用域隐藏(但不替换)父/控制器作用域上的数组。
您需要做的是获取对父数组的引用,然后替换内容(就像您在原始版本中所做的那样)。 See here
答案 4 :(得分:0)
我怀疑是否需要将错误逻辑放在指令中。您可以简单地将错误作为控制器的一部分来处理。除非您在操作警报数组之前绝对需要更换html并删除类,否则您的代码可能会被重写为:
app.directive('spinnerClick', function() {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
var originalHTML = element.html();
var spinnerHTML = "<i class='icon-refresh icon-spin'></i> " + attrs.spinnerText;
function onClickDone(){
element.html(originalHTML).removeClass('disabled');
}
element.click(function() {
if (element.is('.disabled')) {
return;
}
element.html(spinnerHTML).addClass('disabled');
scope.$apply(attrs.spinnerClick).then(onClickDone, onClickDone);
});
}
};
});
app.controller('MainCtrl', function($scope, $q, $timeout) {
$scope.alerts = ['First alert'];
$scope.people = [
{ name: 'David', active: true },
{ name: 'Layla', active: false }
];
$scope.deleteItem = function(index) {
var defer = $q.defer();
$timeout(function() {
defer.reject(["Something 'bad' happened.", "Check your logs."]);
}, 2000);
return defer.promise.then(function(){
//Success handler
}, function(error){
$scope.alerts.length = 0;
$scope.alerts.push(error);
});
};
});