Hi,
I have <form> with inner <ng-form>
when form is submitted inner <ng-form>.$submitted is not set to true only parent
I don't think we should expect the inner ngForm
to behave exactly like the <form>
thing. I guess we could fix this one but would be cool to know what is your real-life use-case?
Hi
I create wizard interface,
there are two directives, which I use to show errors after form is submitted
.directive('bkErrorText', function () {
return {
require: '^form',
template: '<span></span>',
restrict: 'E',
link: function ($scope, $element, $attrs, ctrl) {
var inputCtrl = ctrl[$attrs.bkInputName];
if (!inputCtrl) {
throw 'Can\'t find input ' + $attrs.bkInputName + ' into form.';
}
function showErrorText(newValue, oldValue) {
if (inputCtrl.$invalid && (ctrl.$submitted || ctrl.$$parentForm.$submitted)) {
var i, key, e, etext = [], error = inputCtrl.$error;
for (i in error) {
if (error[i]) {
key = 'bkMsg' + angular.uppercase(i.charAt(0)) + i.substr(1);
e = $attrs[key] || (key + ' no message text!');
etext.push(e);
etext.push(' ');
}
}
$element.html(etext.join(''));
$element.removeClass('hidden');
}
else {
if (!$element.hasClass('hidden')) {
$element.addClass('hidden');
$element.html('');
}
}
};
$scope.$watchCollection(function () { return inputCtrl.$error; }, showErrorText);
$scope.$watch(function () {
return inputCtrl.$invalid && (ctrl.$submitted || ctrl.$$parentForm.$submitted);
}, showErrorText);
}
};
})
.directive('bkHasError', function () {
return {
require: '^form',
restrict: 'A',
scope: {
bkHasError: '@',
bkHasErrorClass: '@'
},
link: function ($scope, $element, $attrs, ctrl) {
var cssClass = $scope.bkHasErrorClass || 'has-error',
inputCtrl = ctrl[$scope.bkHasError];
$scope.$watch(function () {
return inputCtrl.$invalid && (ctrl.$submitted || ctrl.$$parentForm.$submitted);
}, function (newValue, oldValue) {
if (newValue) {
$element.addClass(cssClass);
}
else {
$element.removeClass(cssClass);
}
});
}
};
})
<form class="form-horizontal" role="form" novalidate="novalidate" ng-submit="submitForm()">
<tabset>
<tab select="tabs['common']=true" deselect="tabs['common']=false" active="tabs['common']">
<tab-heading ng-class="{'text-danger':tabsValid['common']===false}">
袨斜褖懈
</tab-heading>
<ng-form name="common" bk-submit-valid="tabsValid['common']">
<div class="form-group" bk-has-error="amountBuy">
<label class="col-md-2 control-label" for="amountBuy">amountBuy</label>
<div class="col-md-3">
<input class="form-control bk-number-lg" id="amountBuy" name="amountBuy" type="text"
ng-model="model.amountBuy"
bk-number="15,2"
bk-number-range="0.01,999999.99"
ng-model-options="{ debounce: 300 }" />
<bk-error-text class="help-block"
bk-input-name="amountBuy"
bk-msg-required="Data is required ..."
bk-msg-number="Number(15,2) ..."
bk-msg-number-range="Number range from 0.01 to 999 999.99 ..." />
</div>
</div>
<!--
others input fields
-->
</ng-form>
</tab>
<!--
others tabs
-->
</tabset>
<div class="col-md-offset-2 col-md-6">
<button class="btn btn-default" type="button" ng-click="prevTab()">Prev</button>
<button class="btn btn-default" type="button" ng-click="nextTab()">Next</button>
<button class="btn btn-primary" type="submit" bk-disable-pristine>Save</button>
</div>
</form>
Another approach is using css:
.ng-submitted .ng-invalid {
.... styles styles styles ...
}
or looping through all parent forms:
while (form) form = form.$$parentForm;
:+1: I would like to see that too
My rl scenario is that I have a form where I have directive that uses ng-form and inside it I also have another directive with some input fields. That second (most inner) directive requires form controller in link function and it is the form controller from the ng-form directive. I have validation messages showing on submitted form but the only form that gets $submitted flag is the most outer form. The ng-form controller has $submitted set to false and the validation messages are not shown.
@kwypchlo does the css I posted above solve your issue?
real life use-case:
some form:
<form ng-form="form1" ng-submit="save(form1)">
<custom-fieldset model="model.firstname" required></custom-fieldset>
<custom-fieldset model="model.lastname" required></custom-fieldset>
<button>save</button>
</form>
custom-fieldset.html
:
<fieldset ng-form="form">
<input name="name" required ng-model="ctrl.model">
<ng-messages role="alert"
for="(form.$submitted || form.name.$touched) && form.name.$error">
<ng-message when="required">please enter a name</ng-message>
</ng-messages>
</fieldset>
@stryju This is similar to the use case I had.
@awerlang nope, I need this functionality in js, inside my directive. The workaround with $$parentForm may work but sure doesn't look elegant :)
:+1: for including this.
I have a setup similar to @stryju 's above. I added a watch to the child forms for now to watch the parent form's $submitted property, but It does feel hacky.
I ran into this issue as well. I have a subform that I reuse in many forms, there it is excluded to refer to parent form name to know if it was submitted or not.
For example, when I submit my user form, obviouly my subform for user address is submitted as well. However, userForm.$submitted becomes true, but userForm.addressForm.$submitted stays to false.
user-form.html:
<form name="userForm" novalidate>
<div ng-class="{ 'has-error' : userForm.$submitted && userForm.firstName.$invalid}">
<label>First name</label>
<input type="text" ng-model="user.firstName" name="firstName" required>
</div>
<div ng-class="{ 'has-error' : userForm.$submitted && userForm.lastName.$invalid}">
<label>Last name</label>
<input type="text" ng-model="user.lastName" name="lastName" required>
</div>
<fieldset ng-form="addressForm" class="address" ng-include="'address/address-fields.html'"></fieldset>
</form>
address-fields.html:
<div ng-class="{ 'has-error' : addressForm.$submitted && addressForm.postcode.$invalid}">
<label>Postcode</label>
<input type="text" ng-model="address.postcode" ng-required="true" name="postcode">
</div>
<div ng-class="{ 'has-error' : addressForm.$submitted && addressForm.city.$invalid}">
<label>City</label>
<input type="text" ng-model="address.city" ng-required="true" name="city">
</div>
<div ng-class="{ 'has-error' : addressForm.$submitted && addressForm.country.$invalid}">
<label>Country</label>
<input type="text" ng-model="address.country" ng-required="true" name="country">
</div>
+1
Yes please!
quick workaround I've found somewhere and modified:
// sets all children ng-forms submitted (no such default functionality)
function setSubmitted(form) {
form.$setSubmitted();
angular.forEach(form, function(item) {
if(item && item.$$parentForm === form && item.$setSubmitted) {
setSubmitted(item);
}
});
}
so ie. instead of scope.form.$setSubmitted(); use:
setSubmitted(scope.form);
A workaround: http://stackoverflow.com/a/30138961/2075423
Note this only works if formCtrl.$submitted
is changed. So it might not work with dynamic nested ng-form
.
I have thesame issue. I made the following workaround based on awerlang's suggestion...
In my directive.
ctrl.isSubmitted = function() {
var form = ctrl.form;
while (!!form) {
if (form.$submitted) return true;
form = form.$$parentForm;
}
return false;
};
In the view...
<div class="has-feedback" ng-class="{'has-error': ctrl.isSubmitted() && ctrl.form[ctrl.name].$invalid}">
...
</div>
I do agree that this feels a bit hacky though, but it works...
The css rules would work as well I think, but the has-error class is defined in bootstrap, and I don't really want to duplicate the css from bootstrap for a custom css rule. That can get messy when migrating to newer bootstrap versions.
will this issue be resolved before Trump takes office?
nope...
"_bump_"
while (form) form = form.$$parentForm;
FYI: This doesn't actually work -- the root FormController
has a "noop form" set as its parent; different looping and/or conditional are required.
(e.g. while (form.$$parentForm.hasOwnProperty('$submitted')) form = form.$$parentForm
or such).
Note, Angular doesn't expose a static binding for FormController
(or any of the types it claims to in the documentation; NgModelController
included), otherwise one could use types to check the $$parentForm
for real-ness.
Another workaround I used was a watch:
$scope.$watch('form.$$parentForm.$submitted', function (submitted) {
if (submitted) {
$scope.form.$setSubmitted(true);
}
});
My team got into a situation where we really needed the change from https://github.com/angular/angular.js/pull/15778, so what we did was to create a copy of our installed angular.js (say, angular-mod.js), applied the change from the PR and used npm's _postinstall_ to overwrite the original angular.js file in node_modules (a simple cp angular-mod.js node_modules/angular/angular.js
).
Worked like a charm.
Most helpful comment
:+1: I would like to see that too
My rl scenario is that I have a form where I have directive that uses ng-form and inside it I also have another directive with some input fields. That second (most inner) directive requires form controller in link function and it is the form controller from the ng-form directive. I have validation messages showing on submitted form but the only form that gets $submitted flag is the most outer form. The ng-form controller has $submitted set to false and the validation messages are not shown.