Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit a0ae07b

Browse files
shahatapetebacondarwin
authored andcommitted
feat(FormController): commit $viewValue of all child controls when form is submitted
Use the new `NgModelController.$commitViewValue()` method to commit the `$viewValue` on all the child controls (including nested `ngForm`s) when the form receives the `submit` event. This will happen immediately, overriding any `updateOn` and `debounce` settings from `ngModelOptions`. If you wish to access the committed `$modelValue`s then you can use the `ngSubmit` directive to provide a handler. Don't use `ngClick` on the submit button, as this handler would be called before the pending `$viewValue`s have been committed. Closes #7017
1 parent adfc322 commit a0ae07b

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

src/ng/directive/form.js

+28-3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,23 @@ function FormController(element, attrs, $scope, $animate) {
7474
$animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
7575
}
7676

77+
/**
78+
* @ngdoc method
79+
* @name form.FormController#$commitViewValue
80+
*
81+
* @description
82+
* Commit all form controls pending updates to the `$modelValue`.
83+
*
84+
* Updates may be pending by a debounced event or because the input is waiting for a some future
85+
* event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
86+
* usually handles calling this in response to input events.
87+
*/
88+
form.$commitViewValue = function() {
89+
forEach(controls, function(control) {
90+
control.$commitViewValue();
91+
});
92+
};
93+
7794
/**
7895
* @ngdoc method
7996
* @name form.FormController#$addControl
@@ -286,6 +303,10 @@ function FormController(element, attrs, $scope, $animate) {
286303
* hitting enter in any of the input fields will trigger the click handler on the *first* button or
287304
* input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
288305
*
306+
* Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
307+
* submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
308+
* to have access to the updated model.
309+
*
289310
* @param {string=} name Name of the form. If specified, the form controller will be published into
290311
* related scope, under this name.
291312
*
@@ -381,19 +402,23 @@ var formDirectiveFactory = function(isNgForm) {
381402
// IE 9 is not affected because it doesn't fire a submit event and try to do a full
382403
// page reload if the form was destroyed by submission of the form via a click handler
383404
// on a button in the form. Looks like an IE9 specific bug.
384-
var preventDefaultListener = function(event) {
405+
var handleFormSubmission = function(event) {
406+
scope.$apply(function() {
407+
controller.$commitViewValue();
408+
});
409+
385410
event.preventDefault
386411
? event.preventDefault()
387412
: event.returnValue = false; // IE
388413
};
389414

390-
addEventListenerFn(formElement[0], 'submit', preventDefaultListener);
415+
addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
391416

392417
// unregister the preventDefault listener so that we don't not leak memory but in a
393418
// way that will achieve the prevention of the default action.
394419
formElement.on('$destroy', function() {
395420
$timeout(function() {
396-
removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
421+
removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
397422
}, 0, false);
398423
});
399424
}

test/ng/directive/formSpec.js

+51
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,57 @@ describe('form', function() {
154154
}).toThrowMinErr('ng', 'badname');
155155
});
156156

157+
describe('triggering commit value on submit', function () {
158+
it('should trigger update on form submit', function() {
159+
var form = $compile(
160+
'<form name="test" ng-model-options="{ updateOn: \'\' }" >' +
161+
'<input type="text" ng-model="name" />' +
162+
'</form>')(scope);
163+
scope.$digest();
164+
165+
var inputElm = form.find('input').eq(0);
166+
changeInputValue(inputElm, 'a');
167+
expect(scope.name).toEqual(undefined);
168+
browserTrigger(form, 'submit');
169+
expect(scope.name).toEqual('a');
170+
dealoc(form);
171+
});
172+
173+
it('should trigger update on form submit with nested forms', function() {
174+
var form = $compile(
175+
'<form name="test" ng-model-options="{ updateOn: \'\' }" >' +
176+
'<div class="ng-form" name="child">' +
177+
'<input type="text" ng-model="name" />' +
178+
'</div>' +
179+
'</form>')(scope);
180+
scope.$digest();
181+
182+
var inputElm = form.find('input').eq(0);
183+
changeInputValue(inputElm, 'a');
184+
expect(scope.name).toEqual(undefined);
185+
browserTrigger(form, 'submit');
186+
expect(scope.name).toEqual('a');
187+
dealoc(form);
188+
});
189+
190+
it('should trigger update before ng-submit is invoked', function() {
191+
var form = $compile(
192+
'<form name="test" ng-submit="submit()" ' +
193+
'ng-model-options="{ updateOn: \'\' }" >' +
194+
'<input type="text" ng-model="name" />' +
195+
'</form>')(scope);
196+
scope.$digest();
197+
198+
var inputElm = form.find('input').eq(0);
199+
changeInputValue(inputElm, 'a');
200+
scope.submit = jasmine.createSpy('submit').andCallFake(function() {
201+
expect(scope.name).toEqual('a');
202+
});
203+
browserTrigger(form, 'submit');
204+
expect(scope.submit).toHaveBeenCalled();
205+
dealoc(form);
206+
});
207+
});
157208

158209
describe('preventing default submission', function() {
159210

0 commit comments

Comments
 (0)