AngularJS:未从指令调用 ngChange

AngularJS: ngChange not called from directive

我的问题简介

我有一个动态显示复选框列表的指令。它有一个名为 options 的参数,该参数应该是如下所示的数组,以便正确显示复选框列表。例如:

var options = [
    {
        id: 1,
        label: 'option #1'
    },
    {
        id: 2,
        label: 'option #2'
    },
    {
        id: 3,
        label: 'option #3'
    }
];

因此,通过将此数组传递给我的指令,将显示一组三个复选框。

此外,该指令要求 ngModel 将具有 checking/unchecking 复选框的结果(此对象始终通过初始化)。例如:

var result = {
    "1": true,
    "2": true,
    "3": false
};

这种情况意味着第一个和第二个复选框(带有 id=1id=2 的选项)被选中,第三个(带有 id=3 的选项)未被选中。

我的指令

template.html

<div ng-repeat="option in options track by $index">
    <div class="checkbox">
        <label>
            <input type="checkbox"
                   ng-model="result[option.id]">
                {{ ::option.label }}
        </label>
    </div>
</div>

directive.js

angular
    .module('myApp')
    .directive('myDirective', myDirective);

function myDirective() {

    var directive = {
        templateUrl: 'template.html',
        restrict: 'E',
        require: 'ngModel',
        scope: {
            options: '='
        },
        link: linkFunc
    };

    return directive;

    function linkFunc(scope, element, attrs, ngModel) {
        scope.result;

        ngModel.$render = setResult;

        function setResult() {
            scope.result = ngModel.$viewValue;
        };

    };

};

我想达到的目标

无论我在哪里使用我的指令,我都希望能够在 ngModel 更改时触发一个函数。当然,我想使用 ngChange 来实现这一点。到目前为止,我有以下内容:

<my-directive
    name="myName"
    options="ctrlVM.options"
    ng-model="ctrlVM.result"
    ng-change="ctrlVM.selectionChanged()">
</my-directive>

但是.selectionChanged()函数不会在模型改变时触发。任何人都知道为什么这不能像我期望的那样工作?

首先,请尽量提供jsfiddle、codepen等代码片段link,以便其他人可以轻松回答您的问题。

您的问题是您在传递对象的引用时从未更新 ctrlVM.result 对象,并且即使您通过调用 ngModel.$setViewValue() 手动更新模型,该引用也永远不会改变。

要解决这个问题,只需通过手动调用 ngModel.$setViewValue() 更新模型并传入新对象,这样引用就会发生变化,这将触发 ngChange 指令逻辑。

我已经添加了这样做的逻辑,它将成功触发更改。看下面的代码:

angular
  .module('myApp', [])
  .directive('myDirective', myDirective)
  .controller('MyController', function($timeout) {
    var vm = this;

    vm.options = [{
      id: 1,
      label: 'option #1'
    }, {
      id: 2,
      label: 'option #2'
    }, {
      id: 3,
      label: 'option #3'
    }];

    vm.result = {
      "1": true,
      "2": true,
      "3": false
    };

    vm.selectionChanged = function() {
      vm.isChanged = true;
      $timeout(function() {
        vm.isChanged = false;
      }, 500)
    }

  });

function myDirective() {

  var directive = {
    templateUrl: 'template.html',
    restrict: 'E',
    require: 'ngModel',
    scope: {
      options: '='
    },
    link: linkFunc
  };

  return directive;

  function linkFunc(scope, element, attrs, ngModel) {
    scope.result;

    ngModel.$render = setResult;

    function setResult() {
      scope.result = ngModel.$viewValue;
    };

    scope.updateValue = function(val) {
      ngModel.$setViewValue(Object.assign({}, val))
    }

  };

};
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>

<div ng-app="myApp">

  <script type="text/ng-template" id="template.html">
    <div ng-repeat="option in options track by $index">
      <div class="checkbox">
        <label>
            <input type="checkbox"
                   ng-model="result[option.id]" ng-click="updateValue(result)">
                {{ ::option.label }}
        </label>
      </div>
    </div>
  </script>

  <div ng-controller="MyController as ctrlVM">

    <my-directive name="myName" options="ctrlVM.options" ng-model="ctrlVM.result" ng-change="ctrlVM.selectionChanged()">
    </my-directive>

    <div> Data: {{ctrlVM.result}} </div>

    <div> isChanged: {{ctrlVM.isChanged}} </div>

  </div>
</div>

@Gaurav 正确识别了问题(ng-change 从未被调用,因为对象引用没有改变)。这是一个更简单的解决方案,不需要手动克隆到控制器的模型中:

  1. ng-change 属性添加绑定:

    scope: {
        options: '=',
        ngChange: '&'  // Add this, creates binding to `ctrlVM.selectionChanged()`
    }
    
  2. ng-change 添加到您的复选框模板:

    <input type="checkbox"
                 ng-model="result[option.id]" ng-change="ngChange()">
    

现在,当任何复选框发生变化时,它会自动调用外部 ng-change 函数,而无需克隆到模型的中间步骤。