2

现场演示

考虑以下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在指令中设置了一种非常讨厌的方式。你能找到更好的方法来做到这一点吗?


更新 :(演示

我试着这样使用$parse

var errorsModel = $parse(attrs.spinnerErrors);
errorsModel.assign(scope, errors);

这不起作用。

但是,如果我有spinner-errors="wrapper.alerts"而不是spinner-errors="alerts"它确实有效!

有没有办法避免使用包装器?

4

5 回答 5

3

我认为您可以更简单地使用隔离范围来做到这一点。

而不是scope: true,,您应该输入:

scope:{
    spinnerClick:"&",
    spinnerText : "@",
    spinnerErrors: "="
 }

然后,在您的指令中直接使用scope.spinnerClick, 。scope.spinnerTextscope.spinnerErrors

&用于绑定属性中定义的函数表达式并将其传递给指令的范围,@绑定属性的文本值,=将与属性中传递的表达式设置双重绑定。

您可以在这里http://docs.angularjs.org/guide/directive进行更精确的解释(查看长版),并在此处进行更清晰的解释http://www.egghead.io/(查看隔离范围视频,只需要几分钟,让它看起来很简单)。

于 2013-07-11T14:26:44.343 回答
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,因为

子作用域拥有自己的属性,该属性隐藏/隐藏同名的父属性。您的解决方法是

  1. 在模型的父对象中定义对象,然后在子对象中引用该对象的属性:parentObj.someProp
  2. 使用 $parent.parentScopeProperty (并非总是可能,但比 1 更容易。如果可能的话)
  3. 在父作用域上定义一个函数,并从子作用域调用它(并不总是可能的)

有一个详细的解释可以在这里找到。

于 2013-07-13T14:06:38.267 回答
0

你对 $parse 有正确的想法。问题是您将新的错误数组分配给子作用域,它隐藏(但不替换)父/控制器作用域上的数组。

您要做的是获取对父数组的引用,然后替换内容(就像您在原始版本中所做的那样)。见这里

于 2013-07-11T17:08:11.137 回答
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);
    });
  };
});
于 2013-07-12T06:54:01.630 回答
0

一种选择是在控制器中创建一个 setter 函数,您可以在指令中调用该函数。然后可以使用对子范围中函数的引用来设置父范围中的值。另一种选择是创建一个隔离范围,然后使用&绑定传入一个 setter 函数。

于 2013-07-07T13:39:34.287 回答