Skip to content

Commit dd07ac1

Browse files
author
Gonzalo Ruiz de Villa
committed
feat(form): add support for ngFormOptions attribute and form isolation
Child forms propagate always their state to its parent form. Following the pattern of ngModelOptions a new optional attribute is defined for forms, ngFormOptions that will allow to define now if the form should be considered as 'root', therefore preventing the propagation of its state to its parent. In the future, if more options are needed this new attribute ngFormOptions may be the place to define them. Options are exposed in the controller, but the isolated property is read only when the NgFormController is executed, so the behavior won't change if its value is changed later. It maybe used like this: <ng:form name="parent"> <ng:form name="child" ng-form-options="{root:true}"> <input ng:model="modelA" name="inputA"> <input ng:model="modelB" name="inputB"> </ng:form> </ng:form> Closes: angular#5858
1 parent 119ace4 commit dd07ac1

File tree

2 files changed

+118
-1
lines changed

2 files changed

+118
-1
lines changed

src/ng/directive/form.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
6464
var form = this,
6565
controls = [];
6666

67-
var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;
67+
form.$options = $scope.$eval(attrs.ngFormOptions) || {};
68+
69+
var parentForm = form.$$parentForm =
70+
(!form.$options.root && element.parent().controller('form'))
71+
|| nullFormCtrl;
6872

6973
// init state
7074
form.$error = {};
@@ -296,6 +300,10 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
296300
*
297301
* @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
298302
* related scope, under this name.
303+
* @param {Object} ngFormOptions options to apply to the current form.
304+
* - `root`: boolean value which indicates that the form should be considered as a root
305+
* and that it should not propagate its state to its parent form (if there is one). By default,
306+
* child forms propagate their state ($dirty, $pristine, $valid, ...) to its parent form.
299307
*
300308
*/
301309

test/ng/directive/formSpec.js

+109
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,114 @@ describe('form', function() {
870870
expect(scope.form.$submitted).toBe(false);
871871
});
872872
});
873+
874+
describe('ngFormOptions attributes', function() {
875+
it('should allow define a form as root', function() {
876+
doc = jqLite(
877+
'<ng:form name="parent">' +
878+
'<ng:form name="child" ng-form-options="{root:true}">' +
879+
'<input ng:model="modelA" name="inputA">' +
880+
'<input ng:model="modelB" name="inputB">' +
881+
'</ng:form>' +
882+
'</ng:form>');
883+
$compile(doc)(scope);
884+
885+
var parent = scope.parent,
886+
child = scope.child,
887+
inputA = child.inputA,
888+
inputB = child.inputB;
889+
890+
inputA.$setValidity('MyError', false);
891+
inputB.$setValidity('MyError', false);
892+
expect(parent.$error.MyError).toBeFalsy();
893+
expect(child.$error.MyError).toEqual([inputA, inputB]);
894+
895+
inputA.$setValidity('MyError', true);
896+
expect(parent.$error.MyError).toBeFalsy();
897+
expect(child.$error.MyError).toEqual([inputB]);
898+
899+
inputB.$setValidity('MyError', true);
900+
expect(parent.$error.MyError).toBeFalsy();
901+
expect(child.$error.MyError).toBeFalsy();
902+
903+
child.$setDirty();
904+
expect(parent.$dirty).toBeFalsy();
905+
906+
child.$setSubmitted();
907+
expect(parent.$submitted).toBeFalsy();
908+
});
909+
910+
it('should chain nested forms as default behaviour', function() {
911+
doc = jqLite(
912+
'<ng:form name="parent">' +
913+
'<ng:form name="child" ng-form-options="{}">' +
914+
'<input ng:model="modelA" name="inputA">' +
915+
'<input ng:model="modelB" name="inputB">' +
916+
'</ng:form>' +
917+
'</ng:form>');
918+
$compile(doc)(scope);
919+
920+
var parent = scope.parent,
921+
child = scope.child,
922+
inputA = child.inputA,
923+
inputB = child.inputB;
924+
925+
inputA.$setValidity('MyError', false);
926+
inputB.$setValidity('MyError', false);
927+
expect(parent.$error.MyError).toEqual([child]);
928+
expect(child.$error.MyError).toEqual([inputA, inputB]);
929+
930+
inputA.$setValidity('MyError', true);
931+
expect(parent.$error.MyError).toEqual([child]);
932+
expect(child.$error.MyError).toEqual([inputB]);
933+
934+
inputB.$setValidity('MyError', true);
935+
expect(parent.$error.MyError).toBeFalsy();
936+
expect(child.$error.MyError).toBeFalsy();
937+
938+
child.$setDirty();
939+
expect(parent.$dirty).toBeTruthy();
940+
941+
child.$setSubmitted();
942+
expect(parent.$submitted).toBeTruthy();
943+
});
944+
945+
it('should chain nested forms when "root" is false', function() {
946+
doc = jqLite(
947+
'<ng:form name="parent">' +
948+
'<ng:form name="child" ng-form-options="{root:false}">' +
949+
'<input ng:model="modelA" name="inputA">' +
950+
'<input ng:model="modelB" name="inputB">' +
951+
'</ng:form>' +
952+
'</ng:form>');
953+
$compile(doc)(scope);
954+
955+
var parent = scope.parent,
956+
child = scope.child,
957+
inputA = child.inputA,
958+
inputB = child.inputB;
959+
960+
inputA.$setValidity('MyError', false);
961+
inputB.$setValidity('MyError', false);
962+
expect(parent.$error.MyError).toEqual([child]);
963+
expect(child.$error.MyError).toEqual([inputA, inputB]);
964+
965+
inputA.$setValidity('MyError', true);
966+
expect(parent.$error.MyError).toEqual([child]);
967+
expect(child.$error.MyError).toEqual([inputB]);
968+
969+
inputB.$setValidity('MyError', true);
970+
expect(parent.$error.MyError).toBeFalsy();
971+
expect(child.$error.MyError).toBeFalsy();
972+
973+
child.$setDirty();
974+
expect(parent.$dirty).toBeTruthy();
975+
976+
child.$setSubmitted();
977+
expect(parent.$submitted).toBeTruthy();
978+
});
979+
980+
});
873981
});
874982

875983
describe('form animations', function() {
@@ -947,4 +1055,5 @@ describe('form animations', function() {
9471055
assertValidAnimation($animate.queue[2], 'addClass', 'ng-valid-custom-error');
9481056
assertValidAnimation($animate.queue[3], 'removeClass', 'ng-invalid-custom-error');
9491057
}));
1058+
9501059
});

0 commit comments

Comments
 (0)