Angular ng-repeat with ng-form, accessing validation in controller
Asked Answered
L

1

33

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?

Lynnet answered 3/6, 2014 at 16:39 Comment(0)
A
44

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 ngFormto 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

Assignor answered 5/6, 2014 at 1:42 Comment(7)
today interpolation works - see source: github.com/angular/angular.js/blob/master/src/ng/directive/… - you can get $dirty $invalid states for specific nested ngForm by name. A lot of possibilities.Nicolais
@LeblancMeneses Thanks for that, it looks like interpolation support was added in Angular 1.3.Assignor
Thanks. However, support for interpolation within ngForm and <input name=""> appears to be of limited value in solving this problem if you're using ngMessages. ng-messages does not support interpolation, so there's no way to refer to a specific form within an ngMessages element - it just throws an error.Heterochromosome
Dynamically created forms are not available in the controller. Sometime only if scope variable are declared in the controller are available from view to controller. I am getting undefined for the dynamically created variables while accessingAlvertaalves
@JeevaJsb you have to use a name like myforms.namesForm_{{$index}} and initialize it in the controller $scope.myforms = {} so the reference will not be broken and you can use it correctly.Mcmillian
exactly in what version of Angular 1.3, interpolation started to be supported?Waterloo
@Waterloo The dynamic interpolation is available in all Angular 1.3.0+ releases (it was added in 1.3.0-rc.3 so was available as soon as 1.3.0 was "fully" released).Assignor

© 2022 - 2024 — McMap. All rights reserved.