2

I have a simple form that looks like this:

<form name="myForm">
  <input name="field" ng-model="item.field"></input>
  <button ng-click="save()">Save</button>
</form>

In theory, I should be able to change things like validity of fields in the controller based on the form and field, as such:

$scope.save = function(item) {
  item.$save();
  $scope.myForm.field.$setValidity("some-error",false);
};

The problem is that directives at various levels get in the way and may set up multiple child scopes. So while item.field is properly rendered and linked, my ability to do anything from the controller is very limited, e.g. if I wrap my partial in a fancy "loading" directive that hides it and shows "loading" until done.

<loading>
  <form name="myForm">
    <input name="field" ng-model="item.field"></input>
    <button ng-click="save()">Save</button>
  </form>
</loading>

Now my controller that wanted to directly set validity no longer has access to $scope.myForm. Truth is, with possible directives, it never can safely rely on access to $scope.myForm; only a directive can!

@Beyers recommends passing the form as part of the save() call, as <button ng-click="save(myForm)">Save</button>, which definitely works, but gets cumbersome.

Is there a real Angular way to do this cleanly? Should I be resetting to $pristine inside my controller directly, or is there some other thing to do?

UPDATE:

I am beginning to wonder if this is the right approach at all. Should my controller be doing this like $setValidity on a form? Shouldn't it take input from the view and modify business objects (item) on that basis, as well as interact with services? How does angular know to reset ng-dirty or ng-invalid for its own validations?

deitch
  • 14,019
  • 14
  • 68
  • 96

2 Answers2

0

I just grabbed the form field by Id last time I did this and it worked fine. Using the ID, you "Angularize" it and then you can access it's model etc...

Like this:

//you will need to add an id to your field.
var myInput = angular.element(document.querySelector('#myInput'));
var ngModelCtrl = myInput.controller('ngModel');

ngModelCtrl.$setValidity("some-error",false);

The angular docs say that you should only do this when you have to, they prefer you to use the ngModel.$validators pipline. Unfortunately I'm not sure how to use that myself so can't help you there.

Caspar Harmer
  • 8,097
  • 2
  • 42
  • 39
  • 2
    I didn't think of doing that, but it can get messy quickly. It feels very old-style jquery-ish. I feel like I am going about this all wrong, that I should be setting the model(s) state in the controller, and the view should adjust itself (including pristine-state) based on tis validity. But not sure how. – deitch Sep 22 '14 at 04:42
0

The problem is that directives at various levels get in the way and may set up multiple child scopes.

You're hitting the standard if you don't have a dot in your model, you're doing it wrong issue. This isn't just true for ng-model, but also for form name.

A solution is to make sure you have an object in your controller for forms, such as forms:

$scope.forms = {};

In the template use forms.<formName>:

<form name="forms.myForm">
  ...
</form>

And then in the controller code to access the form, use $scope.forms.<formName>

$scope.forms.myForm.field.$setValidity("some-error" ,false);

And your updated question:

Should my controller be doing this like $setValidity on a form

I would address this on a case-by-case basis in order to KISS. Validity is often the result of interacting with services, and can be related to business objects/rules. A little bit of code in the controller interacting with validation I would say is ok, but if your controller is getting a bit long with a quite a few responsibilities, then it might then be a good idea to try to move some of the logic into custom directives on the elements that require ngModel. These can then accept parameters/functions via attributes, set by the controller, if required.

Community
  • 1
  • 1
Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
  • @MichaelCharemza, thanks for the detailed response. I know the "dot in your model" problem, but did *not* know that it applied to form name as well. And you are saying that whatever directives are getting in the way can be bypassed the same way as ng-model is applied to the various elements? I will have to try that solution, thank you. – deitch Sep 22 '14 at 11:29
  • I do find that I am doing too much in the directive here. Imagine a simple page that displays info, but when you click "Edit" switches them all to input fields so you can edit them, and converts the "Edit" button to a "Save" and "Cancel". I will add a jsfiddle below. How would I properly do this in a directive? By the by, this edit/save/cancel process occurs in multiple controllers, almost identical. – deitch Sep 22 '14 at 11:35
  • @deitch It sounds like this is leading towards a different question to your original one. If so, I think post as a new question. – Michal Charemza Sep 22 '14 at 11:38
  • Heh, actually it grew out of a different one, I was trying to isolate the problem so the SO question would be very narrowly defined (and thus easier to answer). I hate dumping "this is big and doesn't work" questions on here; I far prefer to isolate the 2-3 particular issues and ask them. :-) – deitch Sep 22 '14 at 11:46
  • In any case, I will post a separate question and include the fiddle on it there – deitch Sep 22 '14 at 11:46
  • Here's the fiddle, will open a separate question with it. http://jsfiddle.net/shp0e10q/1/ – deitch Sep 22 '14 at 12:21
  • And the new question. http://stackoverflow.com/questions/25974097/how-to-isolate-async-validity-checks-from-controller-to-directive-to-solve-dry-a – deitch Sep 22 '14 at 12:27
  • Either way, this specific question is correctly answered, thank you. – deitch Sep 22 '14 at 12:27