From 8ccb58cb29d8328dc534d47eceddaebb959bedfb Mon Sep 17 00:00:00 2001 From: Artur Aleksanyan Date: Fri, 11 Sep 2015 01:24:54 +0400 Subject: [PATCH] feat(ngModel): public method to force run model -> view binding pipeline Added ngModel.NgModelController#$forceModelToViewBound. It triggers $formatters pipeline, and in case of changed $viewValue, runs validators and update the UI without changing actual model. Basically it simulates fake model value change. Closes #12815 --- src/ng/directive/ngModel.js | 26 +++++++++++++++++++++----- test/ng/directive/ngModelSpec.js | 13 +++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/ng/directive/ngModel.js b/src/ng/directive/ngModel.js index aa3a6f532141..b09fecb62b41 100644 --- a/src/ng/directive/ngModel.js +++ b/src/ng/directive/ngModel.js @@ -294,6 +294,21 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ */ this.$render = noop; + /** + * @ngdoc method + * @name ngModel.NgModelController#$forceModelToViewBound + * + * @description + * This might be called to force update model -> view binding without changing actual model + * + * It will trigger $formatters pipeline, and in case of changed $viewValue, will run validators and + * update the UI. + * + * For instance you might use this to change the input's value, but keep the original scope.value the same. + */ + this.$forceModelToViewBound = ngModelWatch.bind(this, true); + + /** * @ngdoc method * @name ngModel.NgModelController#$isEmpty @@ -814,14 +829,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // -> scope value did not change since the last digest as // ng-change executes in apply phase // 4. view should be changed back to 'a' - $scope.$watch(function ngModelWatch() { + $scope.$watch(ngModelWatch); + function ngModelWatch() { var modelValue = ngModelGet($scope); - // if scope model value and ngModel value are out of sync + // if forced to run formatters and validators, or scope model value and ngModel value are out of sync // TODO(perf): why not move this to the action fn? - if (modelValue !== ctrl.$modelValue && + if (arguments[0] === true || (modelValue !== ctrl.$modelValue && // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator - (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) + (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)) ) { ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; parserValid = undefined; @@ -842,7 +858,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ } return modelValue; - }); + } }]; diff --git a/test/ng/directive/ngModelSpec.js b/test/ng/directive/ngModelSpec.js index b45ea2e0c0e0..47722dbda2d5 100644 --- a/test/ng/directive/ngModelSpec.js +++ b/test/ng/directive/ngModelSpec.js @@ -482,6 +482,19 @@ describe('ngModel', function() { }); + it('should be possible to force model -> view binding from ngModelController', function() { + var spyFormatter = jasmine.createSpy('spyValidator'); + ctrl.$formatters = ctrl.$formatters.concat(spyFormatter); + spyOn(ctrl, '$render'); + spyOn(ctrl, '$$runValidators'); + + ctrl.$forceModelToViewBound(); + expect(spyFormatter).toHaveBeenCalledOnce(); + expect(ctrl.$render).toHaveBeenCalledOnce(); + expect(ctrl.$$runValidators).toHaveBeenCalledOnce(); + }); + + it('should clear the view even if invalid', function() { spyOn(ctrl, '$render');