//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 type into the input, I expect the console.log
in loadResults
to log out exactly what I have already typed. It actually logs one character behind because loadResults
is running just before the searchFilter
var in the main controller is receiving the new value from the directive. Logging inside the directive however, everything works as expected. Why is this happening?
My Solution
After getting an understanding of what was happening with ngChange in my simple example, I realized my actual problem was complicated a bit more by the fact that the ngModel I am actually passing in is an object, whose properties i am changing, and also that I am using form validation with this directive as one of the inputs. I found that using $timeout and $eval inside the directive solved all of 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>
You answered your own question in the title!
'='
is watched while'&'
is notSomewhere outside angular:
input view value changes
next digest cycle:
ng-model
value changes and firesng-change()
ng-change adds a $viewChangeListener and is called this same cycle. See: ngModel.js#L714 and ngChange.js implementation.
At that time
$scope.searchFilter
hasn't been updated. Console.log's old valuesearchFilter
is updated by data binding.UPDATE: Only as a POC that you need 1 extra cycle for the value to propagate you can do the following. See the other anwser (@NewDev for a cleaner approach).
The reason for the behavior, as rightly pointed out in another answer, is because the two-way binding hasn't had a chance to change the outer
searchFilter
by the timesearchChange()
, and consequently,loadResults()
was invoked.The solution, however, is very hacky for two reasons.
One, the caller (the user of the directive), should not need to know about these workarounds with
$timeout
. If nothing else, the$timeout
should have been done in the directive rather than in the View controller.And two - a mistake also made by the OP - is that using
ng-model
comes with other "expectations" by users of such directives. Havingng-model
means that other directives, like validators, parsers, formatters and view-change-listeners (likeng-change
) could be used alongside it. To support it properly, one needs torequire: "ngModel"
, rather than bind to its expression viascope: {}
. Otherwise, things would not work as expected.Here's how it's done - for another example, see the official documentation for creating a custom input control.
Then,
ng-change
just automatically works, and so do other directives that supportngModel
, likeng-required
.