如何从Angularjs指令的模板外调用Angularjs指令的方法?

时间:2014-04-17 18:51:29

标签: javascript angularjs angularjs-directive

我是Angularjs的新手所以请耐心等待。

我想创建一个通用向导,让我可以完成一个过程。我开始创建一个自定义指令,处理几乎所有“巫师”的东西......步骤,上一个和下一个等等。

虽然向导模板提供了用于在步骤之间移动的下一个和上一个按钮,但可能有一些操作会导致使用向导的控制器/视图需要向向导指示它需要向前或向后移动步。我一直无法弄清楚如何做到这一点。我想也许$ emit或$ broadcast可能会有效,但我想我可能会遗漏一些有关范围的内容。

看一下这个例子:Plunker example它并不漂亮,但它给了你这个想法。我希望能够做的是当用户选择订单时在向导上强制“下一步”。

以下是我的指令的代码:

    angular.module("app")
    .directive('rhWizard', [wizard])
    .directive('rhStep', [step]);

function wizard() {
    return {
        restrict: "E",
        transclude: true,
        scope: {},
        controller: function ($scope) {
            var steps = $scope.steps = [];
            $scope.selectedIndex = 0;
            $scope.selectedStep = {};
            $scope.$on('wizNext', function () {
                $scope.next();
            });
            $scope.next = function () {
                var result = $scope.selectedStep.completed();
                if (!result)
                    return;

                var _next = $scope.selectedIndex + 1;
                if (_next < steps.length) {
                    $scope.select(steps[_next]);
                }
            };

            $scope.prev = function () {
                var _next = $scope.selectedIndex - 1;
                if (_next > -1) {
                    $scope.select(steps[_next]);
                }
            }

            $scope.select = function (step) {
                for (var x = 0; x < steps.length; x++) {
                    if (steps[x] == step) {
                        $scope.selectedIndex = x;
                        $scope.selectedStep = step;
                        steps[x].selected = true;
                    }
                    else {
                        steps[x].selected = false;
                    }
                }
            };

            this.addStep = function (step) {
                steps.push(step);
                step.id = steps.length;
                if (steps.length === 1) {
                    $scope.select(step);
                }
            };
        },
        templateUrl: "/Scripts/app/common/rhwizard.html"
    }
};

function step() {
    return {
        require: '^rhWizard',
        restrict: 'E',
        transclude: true,
        scope: {
            title: '@',
            completed: '&isComplete',
            nextText: '@',
            prevText: '@'
        },
        link: function (scope, element, attrs, wizCtrl) {
            wizCtrl.addStep(scope);
        },
        templateUrl: '/Scripts/app/common/rhsteps.html'
    };
};

模板代码(rhwizard.html):

<div class="fuelux">
<div class="wizard">
    <ul class="steps">
        <li ng-class="{active: step.selected, complete: step.id <= selectedIndex }" ng-repeat="step in steps" ng-click="select(step)">
            <span class="badge" ng-class="{'badge-info': step.selected, 'badge-success': step.id <= selectedIndex}" >{{step.id}}</span>{{step.title}}<span class="chevron">
            </span>
        </li>
    </ul>
    <div class="actions">
        <button class="btn btn-primary btn-mini" ng-click="prev()" ng-disabled="selectedStep.id == 1"><i class="fa fa-chevron-left"></i> {{selectedStep.prevText || "Prev"}}</button>
        <button class="btn btn-primary btn-mini" ng-click="next()" ng-disabled="selectedStep.id >= steps.length" >{{selectedStep.nextText || "Next" }} <i class="fa fa-chevron-right"></i></button>
    </div>
</div>
<div class="step-content" style="padding-top:20px;" ng-transclude></div>

(rhstep.html)

<div ng-class="{active: selected}" class="step-pane" ng-transclude>
</div>

正在使用的代码片段:

<div ng-app="app" ng-controller="orders as vm">
   <rh-wizard>
       <rh-step title="Select an order">
          <div ng-repeat="order in vm.orders" ng-click="next()"></div>
       </rh-step>
       <rh-step title="Select a filter">
          <p>This is the content</p>
       </rh-step>
   </rh-wizard>
</div>

1 个答案:

答案 0 :(得分:1)

通常通过协调使用服务来处理此类事情的方式。状态由服务管理,并且指令仅响应状态的变化,只要它需要修改DOM,隐藏或显示元素等。外部的控制器也可以注入此服务并调用方法来更改状态,例如向前或向后移动步骤,重置向导等等。

服务可能会广播事件以进行状态更改,例如更改步骤,并且指令会监听这些事件以更新视图。

或者,也许与服务一致,你只需要一个代表巫师状态的众所周知的对象;步骤,哪个步骤是活动的,哪些已经完成等等。然后将该状态作为从控制器到指令的双向绑定范围项。然后,您的指令可以对该范围项执行深度$watch以进行任何更改并适当地显示它们。现在,我提到&#34;与服务&#34;一起。我的意思是服务的操作,例如前进或后退,可以对这个向导状态对象进行操作。这是更可取的,因为您可以制作可以进行单元测试的众所周知的操作,而不是将其留给开发人员以确保他们为他们希望表达的操作适当地修改状态对象。

这两个建议之间的区别在于第一个,状态完全包含在服务中,它只是在消费者希望他们对某个动作作出反应时通知他们。后者稍微宽松一点,因为状态直接在视图控制器和指令控制器之间协调,如果您选择仍然有服务,那么它只能确保服务操作以一致和可靠的方式完成。当然,这些不是你唯一的选择。哪个最好?这取决于您的需求以及您的向导的复杂程度。对于前者,您必须记住重置状态,如果您使用多个向导,请想出一种方法让它知道您的视图正在使用哪个向导。对于多向或复杂的向导系统来说,这似乎是值得的。如果您需要的向导很简单,只使用一次,后者可能会很快而且很脏但很好。

I forked your plunk包含指令和任何其他组件都可以使用的向导服务。

.factory('wizard', function() {
  // private state
  var state = {
    steps: [],
    selectedIndex: 0,
  };

  // public methods for manipulating the state
  return {
    next: function() {
      var i = state.selectedIndex + 1;
      if (i < state.steps.length) this.select(state.steps[i]);
    },
    prev: function() {
      var i = state.selectedIndex - 1;
      if (i > -1) this.select(state.steps[i]);
    },
    select: function(step) {
      for(var x = 0; x < state.steps.length; x++){
        var current = state.steps[x];

        if ((angular.isNumber(step) && step === x) || current == step) {
          current.selected = true;
          state.selectedIndex = x;
        } else {
          current.selected = false;
        }
      }
    },
    addStep: function(step) {
      state.steps.push(step);

      if (state.steps.length === 1) this.select(step);
    },
    // lets a consumer get or set the current state. this is how the wizard directive grabs the state which is binds to in the template
    state: function(newState) {
      if (arguments.length === 0) return state;
      if (newState) {
        state = newState;
        if (!state.steps) state.steps = [];
        if (isNaN(state.selectedIndex)) state.selectedIndex = 0;
      }
    }
  };
})