3

As far as I can tell, ng-change is called before ng-model is actually changed in a select element. Here's some code to reproduce the issue:

angular.module('demo', [])
  .controller('DemoController', function($scope) {
    'use strict';

    $scope.value = 'a';
    $scope.displayValue = $scope.value;

    $scope.onValueChange = function() {
      $scope.displayValue = $scope.value;
    };
  })
  .directive("selector", [
    function() {
      return {
        controller: function() {
          "use strict";
          this.availableValues = ['a', 'b', 'c'];
        },
        controllerAs: 'ctrl',
        scope: {
          ngModel: '=',
          ngChange: '='
        },
        template: '<select ng-model="ngModel" ng-change="ngChange()" ng-options="v for v in ctrl.availableValues"> </select>'
      };
    }
  ]);
<html lang="en">

<head>
  <meta charset="utf-8">
</head>

<body>
  <div ng-app="demo" ng-controller="DemoController">
    <div selector ng-model="value" ng-change="onValueChange">
    </div>
    <div>
      <span>Value when ng-change called:</span>
      <span>{{ displayValue }}</span>
    </div>
  </div>

  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
  <script src="demo.js"></script>
</body>

</html>

If you run this, you should change the combobox 2 times (e.g. 'b' (must be different than the default), then 'c').

  • The first time, nothing happens (but the value displayed in the text should have changed to match the selection).
  • The second time, the value should change to the previous selection (but should have been set to the current selection).

This sounds really similar to a couple previous posts: AngularJS scope updated after ng-change and AngularJS - Why is ng-change called before the model is updated?. Unfortunately, I can't reproduce the first issue, and the second was solved with a different scope binding, which I was already using.

Am I missing something? Is there a workaround?

5
  • try ng-change="ngChange" instead of ng-change="ngChange()"? I think the ngChange() is making a call. Commented Dec 2, 2015 at 7:20
  • why would you want to wrap a <select> element? you start to box off all the really useful configuration you can do for no gain. Commented Dec 2, 2015 at 8:08
  • I suspect that It can be somehow related to this stackoverflow.com/a/31013435/2435473 Commented Dec 2, 2015 at 20:10
  • @CallumLinington: This is just a simplified example to highlight the issue. A real example might be a directive with a <select> element and a few other elements. Commented Dec 3, 2015 at 4:12
  • @tsyu80 if it gets any bigger then i would try and bind controller values to it, i would use the event aggregator pattern Commented Dec 3, 2015 at 8:42

2 Answers 2

3

It's good idea not to use the same model value in the directive. Create another innerModel which should be used inside directive and update the "parent" model when needed with provided NgModelController.

With this solution, you don't hack the ng-change behavior - just use what Angular already provides.

angular.module('demo', [])
  .controller('DemoController', function($scope) {
    $scope.value = 'a';
    $scope.displayValue = $scope.value;
    $scope.onValueChange = function() {
      $scope.displayValue = $scope.value;
    };
  })
  .directive("selector", [
    function() {
      return {
        controller: function() {
          this.availableValues = ['a', 'b', 'c'];
        },
        require: 'ngModel',
        controllerAs: 'ctrl',
        scope: {
          'ngModel': '='
        },
        template: '<select ng-model="innerModel" ng-change="updateInnerModel()" ng-options="v for v in ctrl.availableValues"> </select>',
        link: function(scope, elem, attrs, ngModelController) {
          scope.innerModel = scope.ngModel;
          scope.updateInnerModel = function() {
            ngModelController.$setViewValue(scope.innerModel);
          };
        }
      };
    }
  ]);
<div ng-app="demo" ng-controller="DemoController">
  <div selector ng-model="value" ng-change="onValueChange()">
  </div>
  <div>
    <span>Value when ng-change called:</span>
    <span>{{ displayValue }}</span>
  </div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>

Inspired by this answer.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! This works and makes a lot of sense. Also, thanks for editing my code blocks to be executable; I haven't played around with that yet.
1

It's a digest issue.. Just wrap the onValueChange operations with $timeout:

$scope.onValueChange = function() {
   $timeout(function(){
       $scope.displayValue = $scope.value;        
   });
};
  • Don't forget to inject $timeout in your controller.

... or you can check this link on how to implement ng-change for a custom directive

1 Comment

This seems like a viable solution, but @fracz's solution seemed more correct to me. (I assume his approach is what you were suggesting in your last line.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.