mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
fix(ngModel): deregister from the form on scope not DOM destruction
Due to animations, DOM might get destroyed much later than scope and so the element $destroy event might get fired outside of $digest, which causes changes to the validation model go unobserved until the next digest. By deregistering on scope event, the deregistration always happens in $digest and the form validation model changes will be observed. Closes #4226 Closes #4779
This commit is contained in:
parent
9483373c33
commit
8f989d652f
3 changed files with 94 additions and 18 deletions
|
|
@ -1222,7 +1222,7 @@ var ngModelDirective = function() {
|
|||
|
||||
formCtrl.$addControl(modelCtrl);
|
||||
|
||||
element.on('$destroy', function() {
|
||||
scope.$on('$destroy', function() {
|
||||
formCtrl.$removeControl(modelCtrl);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,18 +36,23 @@ describe('form', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should remove the widget when element removed', function() {
|
||||
it('should remove form control references from the form when nested control is removed from the DOM', function() {
|
||||
doc = $compile(
|
||||
'<form name="myForm">' +
|
||||
'<input type="text" name="alias" ng-model="value" store-model-ctrl/>' +
|
||||
'<input ng-if="inputPresent" name="alias" ng-model="value" store-model-ctrl/>' +
|
||||
'</form>')(scope);
|
||||
scope.inputPresent = true;
|
||||
scope.$digest();
|
||||
|
||||
var form = scope.myForm;
|
||||
control.$setValidity('required', false);
|
||||
expect(form.alias).toBe(control);
|
||||
expect(form.$error.required).toEqual([control]);
|
||||
|
||||
doc.find('input').remove();
|
||||
// remove nested control
|
||||
scope.inputPresent = false;
|
||||
scope.$apply();
|
||||
|
||||
expect(form.$error.required).toBe(false);
|
||||
expect(form.alias).toBeUndefined();
|
||||
});
|
||||
|
|
@ -362,14 +367,15 @@ describe('form', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should deregister a input when its removed from DOM', function() {
|
||||
it('should deregister a input when it is removed from DOM', function() {
|
||||
doc = jqLite(
|
||||
'<form name="parent">' +
|
||||
'<div class="ng-form" name="child">' +
|
||||
'<input ng:model="modelA" name="inputA" required>' +
|
||||
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
|
||||
'</div>' +
|
||||
'</form>');
|
||||
$compile(doc)(scope);
|
||||
scope.inputPresent = true;
|
||||
scope.$apply();
|
||||
|
||||
var parent = scope.parent,
|
||||
|
|
@ -384,7 +390,10 @@ describe('form', function() {
|
|||
expect(doc.hasClass('ng-invalid-required')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-invalid')).toBe(true);
|
||||
expect(doc.find('div').hasClass('ng-invalid-required')).toBe(true);
|
||||
doc.find('input').remove(); //remove child
|
||||
|
||||
//remove child input
|
||||
scope.inputPresent = false;
|
||||
scope.$apply();
|
||||
|
||||
expect(parent.$error.required).toBe(false);
|
||||
expect(child.$error.required).toBe(false);
|
||||
|
|
|
|||
|
|
@ -305,6 +305,84 @@ describe('ngModel', function() {
|
|||
expect(element).toBeInvalid();
|
||||
expect(element).toHaveClass('ng-invalid-required');
|
||||
}));
|
||||
|
||||
|
||||
it('should register/deregister a nested ngModel with parent form when entering or leaving DOM',
|
||||
inject(function($compile, $rootScope) {
|
||||
|
||||
var element = $compile('<form name="myForm">' +
|
||||
'<input ng-if="inputPresent" name="myControl" ng-model="value" required >' +
|
||||
'</form>')($rootScope);
|
||||
var isFormValid;
|
||||
|
||||
$rootScope.inputPresent = false;
|
||||
$rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; });
|
||||
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.myForm.$valid).toBe(true);
|
||||
expect(isFormValid).toBe(true);
|
||||
expect($rootScope.myForm.myControl).toBeUndefined();
|
||||
|
||||
$rootScope.inputPresent = true;
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.myForm.$valid).toBe(false);
|
||||
expect(isFormValid).toBe(false);
|
||||
expect($rootScope.myForm.myControl).toBeDefined();
|
||||
|
||||
$rootScope.inputPresent = false;
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.myForm.$valid).toBe(true);
|
||||
expect(isFormValid).toBe(true);
|
||||
expect($rootScope.myForm.myControl).toBeUndefined();
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
|
||||
it('should register/deregister a nested ngModel with parent form when entering or leaving DOM with animations',
|
||||
function() {
|
||||
|
||||
// ngAnimate performs the dom manipulation after digest, and since the form validity can be affected by a form
|
||||
// control going away we must ensure that the deregistration happens during the digest while we are still doing
|
||||
// dirty checking.
|
||||
module('ngAnimate');
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var element = $compile('<form name="myForm">' +
|
||||
'<input ng-if="inputPresent" name="myControl" ng-model="value" required >' +
|
||||
'</form>')($rootScope);
|
||||
var isFormValid;
|
||||
|
||||
$rootScope.inputPresent = false;
|
||||
// this watch ensure that the form validity gets updated during digest (so that we can observe it)
|
||||
$rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; });
|
||||
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.myForm.$valid).toBe(true);
|
||||
expect(isFormValid).toBe(true);
|
||||
expect($rootScope.myForm.myControl).toBeUndefined();
|
||||
|
||||
$rootScope.inputPresent = true;
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.myForm.$valid).toBe(false);
|
||||
expect(isFormValid).toBe(false);
|
||||
expect($rootScope.myForm.myControl).toBeDefined();
|
||||
|
||||
$rootScope.inputPresent = false;
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.myForm.$valid).toBe(true);
|
||||
expect(isFormValid).toBe(true);
|
||||
expect($rootScope.myForm.myControl).toBeUndefined();
|
||||
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -369,17 +447,6 @@ describe('input', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should cleanup it self from the parent form', function() {
|
||||
compileInput('<input ng-model="name" name="alias" required>');
|
||||
|
||||
scope.$apply();
|
||||
expect(scope.form.$error.required.length).toBe(1);
|
||||
|
||||
inputElm.remove();
|
||||
expect(scope.form.$error.required).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should update the model on "blur" event', function() {
|
||||
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue