NgChange fires before the value leaves the selection area

//main controller angular.module('myApp') .controller('mainCtrl', function ($scope){ $scope.loadResults = function (){ console.log($scope.searchFilter); }; }); // directive angular.module('myApp') .directive('customSearch', function () { return { scope: { searchModel: '=ngModel', searchChange: '&ngChange', }, require: 'ngModel', template: '<input type="text" ng-model="searchModel" ng-change="searchChange()"/>', restrict: 'E' }; }); // html <custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search> 

Here is a simplified directive to illustrate. When I enter the input, I expect that console.log in loadResults will exit exactly what I already typed. It actually registers a single character, because loadResults works just before searchFilter var in the main controller gets a new value from the directive. However, entry into the directive works as you would expect. Why is this happening?

My decision

After I realized what was happening with ngChange in my simple example, I realized that my actual problem was a bit more complicated due to the fact that the ngModel I am actually transitioning to is an object whose properties I am changing, and also that I use form validation with this directive as one of the inputs. I found that using $ timeout and $ eval inside the directive solves all my problems:

 //main controller angular.module('myApp') .controller('mainCtrl', function ($scope){ $scope.loadResults = function (){ console.log($scope.searchFilter); }; }); // directive angular.module('myApp') .directive('customSearch', function ($timeout) { return { scope: { searchModel: '=ngModel' }, require: 'ngModel', template: '<input type="text" ng-model="searchModel.subProp" ng-change="valueChange()"/>', restrict: 'E', link: function ($scope, $element, $attrs, ngModel) { $scope.valueChange = function() { $timeout(function() { if ($attrs.ngChange) $scope.$parent.$eval($attrs.ngChange); }, 0); }; } }; }); // html <custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search> 
+8
angularjs angularjs-ng-change
source share
2 answers

You answered your question in the title! '=' looked at, but '&' not

  • Somewhere outside of angular:

    change input value

  • following digest cycle:

    ng-model The value changes and fails ng-change()

    ng-change adds $ viewChangeListener and is called the same loop. See: ngModel.js # L714 and ngChange.js .

    At that time, $scope.searchFilter not updated. Starting value Console.log

  • next digest cycle: searchFilter updated with data binding.

UPDATE: only as the POC that you need 1 extra loop for distribution, you can do the following. See Another anwser (@NewDev for a cleaner approach).

 .controller('mainCtrl', function ($scope, $timeout){ $scope.loadResults = function (){ $timeout(function(){ console.log($scope.searchFilter); }); }; }); 
+4
source share

The reason for the behavior, as rightly pointed out in another answer, is that the two-way binding did not have the ability to change the external searchFilter to the time of searchChange() and therefore loadResults() was called.

The solution, however, is very hacks for two reasons.

First, the caller (directive user) should not be aware of these workarounds with $timeout . If nothing else, $timeout should have been done in the directive, not in the View controller.

And two - the mistake also made by OP - is that the use of ng-model accompanied by other "expectations" of users of such directives. The presence of ng-model means that other directives can be used next to it, such as validators, parsers, formatting elements and view-change-listeners (for example, ng-change ). To support it correctly, you need require: "ngModel" instead of binding to its expression through scope: {} . Otherwise, everything will not work as expected.

Here's how to do it - in another example, see the official documentation for creating a custom input control.

 scope: true, // could also be {}, but I would avoid scope: false here template: '<input ng-model="innerModel" ng-change="onChange()">', require: "ngModel", link: function(scope, element, attrs, ctrls){ var ngModel = ctrls; // ngModelController // from model -> view ngModel.$render = function(){ scope.innerModel = ngModel.$viewValue; } // from view -> model scope.onChange = function(){ ngModel.$setViewValue(scope.innerModel); } } 

Then ng-change just works, as well as other directives that support ngModel , like ng-required .

+10
source share

All Articles