I am trying to generate an editable list using ng-repeat
. I want to remind the user to update any edits before moving on, so I am using ng-form
to create "nested" forms on the fly because the documentation says I can then use validation on these dynamically created inputs.
While that seems to work within the HTML, I don't see how to access those dynamically created forms and related validation fields in the controller. Specifically, when the user changes the input I use the form $dirty property to bring up a button to tell the user to commit the changes. So far, so good. However, once the changes are committed I want to $setPristine()
on the field to indicate that the changes have been set. There may be other ways of ensuring that changes are committed on each input before I allow the main form committed, but this was the best I could come up with.
Unfortunately, even though the documentation says that if I name the ng-form it will be propagated to the $scope
object, I can't find a way to access it. $scope.dynamic_form
is undefined.
Here is a plunker showing what I mean:
plnk
Thanks!
[EDIT] Just to add to the issue, what does work for this specific example is to add to the ng-click
on the dynamically created input:
ng-click="namesForm.name.$setPristine();clean()"
But I still don't have access to the dynamically created form in the controller. I would like, for example, to add a watcher to the namesForm.name.$pristine
so that I can set the mainForm.$setValidity(false)
whenever the sub-form is $dirty
to prevent the user from submitting the main form until all sub-form changes have been committed.
So in a nutshell, the issue is how to access in a parent controller the validation values of a dynamically created nested ngForm?
Updated 2015-01-17:
As pointed out by Leblanc Meneses in the comments Angular 1.3 now supports interpolation with form
, ngForm
and input
directives.
This means that using expressions to name your elements:
<div ng-form="namesForm_{{$index}}" ng-repeat="name in names">
<input type="text"
name="input_{{$index}}_0"></input>
<!-- ... -->
</div>
will work as expected:
$scope['namesForm_0']
$scope.namesForm_1
// Access nested form elements:
$scope.namesForm_1.input_1_0
...
Original answer for Angular <= 1.2:
Working with forms and the ngFormController
can get tricky pretty quickly.
You need to be aware that you can dynamically add form elements and inputs but they can't be dynamically named - interpolation does not work in the ngForm
or name
directives.
For example, if you tried to name your nested forms dynamically like this:
<div ng-form="namesForm_{{$index}}" ng-repeat="name in names">
<!-- ... -->
</div>
Instead of making all the nested forms available on the scope like this: scope['namesForm_0']
you would only have access to the single (last) form with the literal name scope['namesForm_{{$index}}']
.
In your situation you need to create a custom directive that will be added along with ngForm
to handle setting $pristine$
and $invalid
for that form instance.
JavaScript:
This directive will watch the $dirty
state of its form to set the $validity
to prevent submission when dirty and handle setting the $pristine
state when the 'clean' button is pressed.
app.directive('formCleaner', function(){
return {
scope: true,
require: '^form',
link: function(scope, element, attr){
scope.clean = function () {
scope.namesForm.$setPristine();
};
scope.$watch('namesForm.$dirty', function(isDirty){
scope.namesForm.$setValidity('name', !isDirty);
});
}
};
});
HTML:
Then the only change to your HTML is to add the formCleaner
directive.
So change your original HTML from this:
<body ng-controller="MainCtrl">
<form name="mainForm" submit="submit()">
<h3>My Editable List</h3>
<div ng-form="namesForm"
ng-repeat="name in names">
<!-- ... -->
</div>
<button class="btn btn-default" type="submit">Submit</button>
</form>
</body>
to this, by adding form-cleaner
next to ng-form
:
<body ng-controller="MainCtrl">
<form name="mainForm" submit="submit()">
<h3>My Editable List</h3>
<!-- Add the `form-cleaner` directive to the element with `ng-form` -->
<div form-cleaner
ng-form="namesForm"
ng-repeat="name in names">
<!-- ... -->
</div>
<button class="btn btn-default" type="submit">Submit</button>
</form>
</body>
Here is an updated Plunker showing the new behaviour: http://plnkr.co/edit/Lxem5HJXe0UCvslqbJr3?p=preview