AngularJS - ngClick,自定义指令和隔离范围问题

时间:2013-05-27 14:20:47

标签: angularjs angularjs-directive

请考虑以下指令:(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方式”是什么?

7 个答案:

答案 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 图标。

  • 无需手动指定微调文本
  • 只需在加载
  • 时添加文本左侧的微调内容
  • 我们必须使用终于而不是然后作为承诺,否则微调器会在失败时留在那里?
  • 我必须使用$ compile,因为按钮的内容是动态编译的,因为我正在使用https://github.com/angular-translate/angular-translate

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)
]