It would be great to add some optional attribute to form element or div with ng-form attribute which will say that validation errors of current form shouldn't affect validation of parent form.
Here is what I mean:
<div ng-form="parentForm">
<input...>some valid input</input>
<div ng-form="childForm" omit-parent-validation="true">
<input...>some INVALID input</input>
</div>
</div>
Result:
parentForm.$valid -> true, childForm.$valid -> false
Same logic may be applied for
parentForm.$pristine -> true, childForm.$pristine -> false
This seems like it should be pretty trivial to implement. Care to submit a patch?
What would be an appropriate name for this attribute ? ng-propagate ? ng-isolate ? Any suggestions ?
Probably we would want to prevent the registration of the form in its parent form altogether. I'm thinking: a parent
attribute on ngForm, that defaults to the ngForm that is the parent in the DOM (formElement.parent().controller('form')
in the current implementation). Then, if one defines the attribute but with a falsy value, it wouldn't register itself in another form (this will prevent all propagation of validation, dirtiness and so on). Could then be used the other way around as well; telling ngForm to register itself in a form that is not the actual parent in the DOM.
related #5037
I wrote a temporary solution, waiting the fix from angular.
I use an attribute directive on the nested form that isolates the interaction ($pristine, $dirty) and validity ($valid, $invalid) from its parent form.
Here is the http://jsfiddle.net/gikoo/qNrFX/
angular.module('isolateForm',[]).directive('isolateForm', [function () {
return {
restrict: 'A',
require: '?form',
link: function (scope, elm, attrs, ctrl) {
if (!ctrl) {
return;
}
// Do a copy of the controller
var ctrlCopy = {};
angular.copy(ctrl, ctrlCopy);
// Get the parent of the form
var parent = elm.parent().controller('form');
// Remove parent link to the controller
parent.$removeControl(ctrl);
// Replace form controller with a "isolated form"
var isolatedFormCtrl = {
$setValidity: function (validationToken, isValid, control) {
ctrlCopy.$setValidity(validationToken, isValid, control);
parent.$setValidity(validationToken, true, ctrl);
},
$setDirty: function () {
elm.removeClass('ng-pristine').addClass('ng-dirty');
ctrl.$dirty = true;
ctrl.$pristine = false;
},
};
angular.extend(ctrl, isolatedFormCtrl);
}
};
}]);
You can use it like that:
<form name="parent">
<input type="text" ng-model="outside"/>
<ng-form name="subform" isolate-form>
<input type="text" ng-model="inside"/>
</ng-form>
</form>
Another proposed solution: #8917
:+1:
+1
Implementation done by this guy in SO (link below), seems ideal (in functionality) to me, as it preserves most of the isolated forms functionality. In my project I am using this code as a base for a custom directive, actually the only change I made was add $setPristine.
@rodrigopedra is that solution working for you in AngularJS 1.3?
yep. I did a small change, but just for my use case.
Here is the directive I am using to isolate the form:
/**
* @ref http://stackoverflow.com/a/23541054/1211472
* @ref http://stackoverflow.com/a/24936234/1211472
*/
app.directive(
'rrIsolatedForm', [
function () {
'use strict';
return {
restrict : 'A',
require : '?form',
link : function link ( scope, element, iAttrs, formController ) {
element.addClass( 'rr-isolated-form' );
if ( !formController )
{
return;
}
// Remove this form from parent controller
var parentFormController = element.parent().controller( 'form' );
parentFormController.$removeControl( formController );
if ( !parentFormController )
{
return; // root form, no need to isolate
}
// Do a copy of the controller
var originalCtrl = {};
angular.copy( formController, originalCtrl );
// Replace form controller with a "null-controller"
var nullFormCtrl = {
// $addControl : angular.noop,
// $removeControl : angular.noop,
$setValidity : function ( validationToken, isValid, control ) {
originalCtrl.$setValidity( validationToken, isValid, control );
parentFormController.$setValidity( validationToken, true, formController );
},
$setDirty : function () {
element.removeClass( 'ng-pristine' ).addClass( 'ng-dirty' );
formController.$dirty = true;
formController.$pristine = false;
},
$setPristine : function () {
element.addClass( 'ng-pristine' ).removeClass( 'ng-dirty' );
formController.$dirty = false;
formController.$pristine = true;
}
};
angular.extend( formController, nullFormCtrl );
}
};
}
]
);
If you want I can send you the snippet for the component I am using isolated, it is a list of phones/emails that the user can add/remove in an internal form. The component syncs an array with the outer form through ngModel
only when the collection (array) changes.
I wanted to reach a slightly different result from the one described by this issue: i.e., I just wanted to detach some controls from the containing form. This directive does the job, simply removing the $formController
from the inheritedData
chain and causing the ngModelController
s to register a nullFormCtrl
for their parentForm
:
.directive('evictForm', function() {
return {
restrict : 'A',
link: {
pre: function (scope, iElement) {
iElement.data('$formController', null);
}
}
};
})
I think it might apply to subforms as well, so I'm just posting it here.
Thanks to @chrisirhc for the advice and the discussion.
@alessiodm your solution breaks form from updating itself.
Is angular team going to solve this issue?
There is a PR for this at #10193
+1
Using @91K00 solution.
+1
Using @91K00 solution also!
First of all, thank you to @91K00 and @gonzaloruizdevilla for trying to fix the problem.
lets assume this structure:
<ng-form name="X1" novalidate>
<ng-form name="X2" novalidate isolate-form>
<input name="Input01" ng-model="input1" required />
<p ng-show="X2.Input01.$touched && X2.Input01.$invalid">input is not valid</p>
<input name="Input02" ng-model="input2" required />
<input type="button" id="ButtonX2" value="Submit Nested Form" ng-disabled="X2.$invalid" />
</ng-form>
<input name="Input03" ng-model="input3" required ng-minlength="5" />
<input type="button" id="ButtonX1" value="Submit Nested Form" ng-disabled="X1.$invalid" />
</ng-form>
neither the ngFormTopLevel or @91K00 solution can handle this.
tl;dr :
ButtonX1 is dependent to nested form validation and it shouldn't !
Test case 1:
Step 1: Fill input3 with any text and more than 5 character.
Expected: ButtonX1 should be enable.
Result: ButtonX1 still disabled.
Test case 2:
Step 1: Fill input1 with any text.
Step 2: Fill input2 with any text.
Expected: ButtonX2 should be enable.
Result: ButtonX2 is enabled.
Test case 3:
Step 1: Fill input3 with any text and more than 5 character.
Step 2: Fill input1 with any text.
Step 2: Fill input2 with any text.
Expected: ButtonX1 and ButtonX2 should be enable.
Result: ButtonX1 and ButtonX2 is enabled.
and the other problem is the P tag inside the nested form does not show when the Input01 is invalid.
i tried both the isolateForm and the ngFormTopLevel but both of them have this problem.
Hi rSafari,
If you add "ng-model" attribute on each input text, it works.
For the message inside the P, I don't know why the $touched control is not working... You can use $dirty or ng-message module instead.
Thank you @91K00 for your answer.
of course i have ng-model for my inputs. i just forgot to add it in this structure ... ( i updated my post)
the main problem is that i have to validate the nested form before i can submit the parent form. it means i cant submit the parent form until i add something valid to the nested form inputs.
by the way, the {{X2.Input01.$error}} is empty and don't have $dirty or $invalid object in it.
stackoverflow related topic :
http://stackoverflow.com/questions/37938530/validate-nested-forms-without-affecting-the-parent-form
This solution no longer works (tested in v1.6.2) due to an exception when copying the original form controller.
Error: [ng:cpws] Can't copy! Making copies of Window or Scope instances is not supported.
Any suggestions for a workaround?
also not working on v1.6.3
why do need to copy the form Controller anyway? It should be enough to simply call parent.$removeControl(self) in the directive. If the form does not have a parent form controller, it doesn't propagate change upwards.
Here is working code for angular >=1.6.2, based recent comment. https://github.com/momega/isolate-form
Isolate forms aren't in scope for core because the logic required is too heavy. See https://github.com/angular/angular.js/pull/10193#discussion_r66080985 You can detach form controls, however.
Most helpful comment
I wrote a temporary solution, waiting the fix from angular.
I use an attribute directive on the nested form that isolates the interaction ($pristine, $dirty) and validity ($valid, $invalid) from its parent form.
Here is the http://jsfiddle.net/gikoo/qNrFX/
You can use it like that: