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

Commit

Permalink
feat(FormController): commit $viewValue of all child controls when …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
shahata authored and petebacondarwin committed May 9, 2014
1 parent adfc322 commit a0ae07b
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 3 deletions.
31 changes: 28 additions & 3 deletions src/ng/directive/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ function FormController(element, attrs, $scope, $animate) {
$animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
}

/**
* @ngdoc method
* @name form.FormController#$commitViewValue
*
* @description
* Commit all form controls pending updates to the `$modelValue`.
*
* Updates may be pending by a debounced event or because the input is waiting for a some future
* event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
* usually handles calling this in response to input events.
*/
form.$commitViewValue = function() {
forEach(controls, function(control) {
control.$commitViewValue();
});
};

/**
* @ngdoc method
* @name form.FormController#$addControl
Expand Down Expand Up @@ -286,6 +303,10 @@ function FormController(element, attrs, $scope, $animate) {
* hitting enter in any of the input fields will trigger the click handler on the *first* button or
* input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
*
* Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
* submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
* to have access to the updated model.
*
* @param {string=} name Name of the form. If specified, the form controller will be published into
* related scope, under this name.
*
Expand Down Expand Up @@ -381,19 +402,23 @@ var formDirectiveFactory = function(isNgForm) {
// IE 9 is not affected because it doesn't fire a submit event and try to do a full
// page reload if the form was destroyed by submission of the form via a click handler
// on a button in the form. Looks like an IE9 specific bug.
var preventDefaultListener = function(event) {
var handleFormSubmission = function(event) {
scope.$apply(function() {
controller.$commitViewValue();
});

event.preventDefault
? event.preventDefault()
: event.returnValue = false; // IE
};

addEventListenerFn(formElement[0], 'submit', preventDefaultListener);
addEventListenerFn(formElement[0], 'submit', handleFormSubmission);

// unregister the preventDefault listener so that we don't not leak memory but in a
// way that will achieve the prevention of the default action.
formElement.on('$destroy', function() {
$timeout(function() {
removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
}, 0, false);
});
}
Expand Down
51 changes: 51 additions & 0 deletions test/ng/directive/formSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,57 @@ describe('form', function() {
}).toThrowMinErr('ng', 'badname');
});

describe('triggering commit value on submit', function () {
it('should trigger update on form submit', function() {
var form = $compile(
'<form name="test" ng-model-options="{ updateOn: \'\' }" >' +
'<input type="text" ng-model="name" />' +
'</form>')(scope);
scope.$digest();

var inputElm = form.find('input').eq(0);
changeInputValue(inputElm, 'a');
expect(scope.name).toEqual(undefined);
browserTrigger(form, 'submit');
expect(scope.name).toEqual('a');
dealoc(form);
});

it('should trigger update on form submit with nested forms', function() {
var form = $compile(
'<form name="test" ng-model-options="{ updateOn: \'\' }" >' +
'<div class="ng-form" name="child">' +
'<input type="text" ng-model="name" />' +
'</div>' +
'</form>')(scope);
scope.$digest();

var inputElm = form.find('input').eq(0);
changeInputValue(inputElm, 'a');
expect(scope.name).toEqual(undefined);
browserTrigger(form, 'submit');
expect(scope.name).toEqual('a');
dealoc(form);
});

it('should trigger update before ng-submit is invoked', function() {
var form = $compile(
'<form name="test" ng-submit="submit()" ' +
'ng-model-options="{ updateOn: \'\' }" >' +
'<input type="text" ng-model="name" />' +
'</form>')(scope);
scope.$digest();

var inputElm = form.find('input').eq(0);
changeInputValue(inputElm, 'a');
scope.submit = jasmine.createSpy('submit').andCallFake(function() {
expect(scope.name).toEqual('a');
});
browserTrigger(form, 'submit');
expect(scope.submit).toHaveBeenCalled();
dealoc(form);
});
});

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

Expand Down

0 comments on commit a0ae07b

Please sign in to comment.