mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-06 22:35:22 +08:00
fix(ngModelController): introduce $cancelUpdate to cancel pending updates
The `$cancelUpdate()` method on `NgModelController` cancels any pending debounce action and resets the view value by invoking `$render()`. This method should be invoked before programmatic update to the model of inputs that might have pending updates due to `ng-model-options` specifying `updateOn` or `debounce` properties. Fixes #6994 Closes #7014
This commit is contained in:
committed by
Peter Bacon Darwin
parent
b389cfc49e
commit
940fcb4090
@@ -1577,7 +1577,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
var ngModelGet = $parse($attr.ngModel),
|
||||
ngModelSet = ngModelGet.assign,
|
||||
pendingDebounce = null;
|
||||
pendingDebounce = null,
|
||||
ctrl = this;
|
||||
|
||||
if (!ngModelSet) {
|
||||
throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
|
||||
@@ -1693,19 +1694,26 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$cancelDebounce
|
||||
* @name ngModel.NgModelController#$cancelUpdate
|
||||
*
|
||||
* @description
|
||||
* Cancel a pending debounced update.
|
||||
* Cancel an update and reset the input element's value to prevent an update to the `$viewValue`,
|
||||
* which may be caused by a pending debounced event or because the input is waiting for a some
|
||||
* future event.
|
||||
*
|
||||
* This method should be called before directly update a debounced model from the scope in
|
||||
* order to prevent unintended future changes of the model value because of a delayed event.
|
||||
* If you have an input that uses `ng-model-options` to set up debounced events or events such
|
||||
* as blur you can have a situation where there is a period when the value of the input element
|
||||
* is out of synch with the ngModel's `$viewValue`. You can run into difficulties if you try to
|
||||
* update the ngModel's `$modelValue` programmatically before these debounced/future events have
|
||||
* completed, because Angular's dirty checking mechanism is not able to tell whether the model
|
||||
* has actually changed or not. This method should be called before directly updating a model
|
||||
* from the scope in case you have an input with `ng-model-options` that do not include immediate
|
||||
* update of the default trigger. This is important in order to make sure that this input field
|
||||
* will be updated with the new value and any pending operation will be canceled.
|
||||
*/
|
||||
this.$cancelDebounce = function() {
|
||||
if ( pendingDebounce ) {
|
||||
$timeout.cancel(pendingDebounce);
|
||||
pendingDebounce = null;
|
||||
}
|
||||
this.$cancelUpdate = function() {
|
||||
$timeout.cancel(pendingDebounce);
|
||||
this.$render();
|
||||
};
|
||||
|
||||
// update the view value
|
||||
@@ -1764,25 +1772,21 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @param {string} trigger Event that triggered the update.
|
||||
*/
|
||||
this.$setViewValue = function(value, trigger) {
|
||||
var that = this;
|
||||
var debounceDelay = this.$options && (isObject(this.$options.debounce)
|
||||
? (this.$options.debounce[trigger] || this.$options.debounce['default'] || 0)
|
||||
: this.$options.debounce) || 0;
|
||||
|
||||
that.$cancelDebounce();
|
||||
if ( debounceDelay ) {
|
||||
$timeout.cancel(pendingDebounce);
|
||||
if (debounceDelay) {
|
||||
pendingDebounce = $timeout(function() {
|
||||
pendingDebounce = null;
|
||||
that.$$realSetViewValue(value);
|
||||
ctrl.$$realSetViewValue(value);
|
||||
}, debounceDelay);
|
||||
} else {
|
||||
that.$$realSetViewValue(value);
|
||||
this.$$realSetViewValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
// model -> value
|
||||
var ctrl = this;
|
||||
|
||||
$scope.$watch(function ngModelWatch() {
|
||||
var value = ngModelGet($scope);
|
||||
|
||||
@@ -2293,4 +2297,4 @@ var ngModelOptionsDirective = function() {
|
||||
}
|
||||
}]
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -847,22 +847,48 @@ describe('input', function() {
|
||||
dealoc(doc);
|
||||
}));
|
||||
|
||||
|
||||
it('should allow cancelling pending updates', inject(function($timeout) {
|
||||
it('should allow canceling pending updates', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<form name="test">'+
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ debounce: 10000 }" />'+
|
||||
'</form>');
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ debounce: 10000 }" />');
|
||||
|
||||
changeInputValueTo('a');
|
||||
expect(scope.name).toEqual(undefined);
|
||||
$timeout.flush(2000);
|
||||
scope.test.alias.$cancelDebounce();
|
||||
scope.form.alias.$cancelUpdate();
|
||||
expect(scope.name).toEqual(undefined);
|
||||
$timeout.flush(10000);
|
||||
expect(scope.name).toEqual(undefined);
|
||||
}));
|
||||
|
||||
it('should reset input val if cancelUpdate called during pending update', function() {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ updateOn: \'blur\' }" />');
|
||||
scope.$digest();
|
||||
|
||||
changeInputValueTo('a');
|
||||
expect(inputElm.val()).toBe('a');
|
||||
scope.form.alias.$cancelUpdate();
|
||||
expect(inputElm.val()).toBe('');
|
||||
browserTrigger(inputElm, 'blur');
|
||||
expect(inputElm.val()).toBe('');
|
||||
});
|
||||
|
||||
it('should reset input val if cancelUpdate called during debounce', inject(function($timeout) {
|
||||
compileInput(
|
||||
'<input type="text" ng-model="name" name="alias" '+
|
||||
'ng-model-options="{ debounce: 2000 }" />');
|
||||
scope.$digest();
|
||||
|
||||
changeInputValueTo('a');
|
||||
expect(inputElm.val()).toBe('a');
|
||||
scope.form.alias.$cancelUpdate();
|
||||
expect(inputElm.val()).toBe('');
|
||||
$timeout.flush(3000);
|
||||
expect(inputElm.val()).toBe('');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should allow complex reference binding', function() {
|
||||
|
||||
Reference in New Issue
Block a user