Angular ng-repeat with ng-form, accessing validation in controller
angularjs, angularjs-ng-form, angularjs-ng-repeat, validation
Solution
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
Problem
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?