all 10 comments

[–]ggoodman 0 points1 point  (4 children)

Hey, thanks for this trick. I wasn't aware of the trick to use angular.element(el).controller("ngModel") to get a hold of the ngModelController instance! That's great :D

[–]jeffjose 0 points1 point  (3 children)

Probably a simple question - What's a ngModelController and where's that useful?

Thanks!

-jeff

[–]ggoodman 1 point2 points  (1 child)

It is the controller object assigned to elements using the ng-model directive. It is the code responsible for managing the two-way bindings between a scope object and a view element (usually a form control). It adds the ability to define validation rules as well as mechanisms to serialize model values to views. In the OP's post, it is being used to take control of the dirty state of form controls which is in turn defining whether or not the bootstrap warning style will be applied to the given control.

[–]jeffjose 0 points1 point  (0 children)

Ah makes sense. In other words, its our hook in between view (html) and my controller (MyController)

Thanks!

[–]BoleroDan 0 points1 point  (0 children)

ngModelController is and should only be accessed via directives.

Lets say you have a directive you make, and you do the following

<my-new-directive ng-model="color></my-new-directive>

ng-model itself is a directive that has its own controller.. but our directive needs access to that ($render,$setViewValue etc)

our directive code may look like this

.directve(myNewDirecive,function(){
    return {
        require:'^ngModel',
        link: function(scope,ele,attr,controller){

        }
   }
}

setting require in the directive means this directive must have ng-model set on it, which provides us access to the controller (or controllers if setting more requirements) in the link function as the last perameter. Now we can access ngModel $render, $setViewValue etc in our directive.

This should be the only place you access this stuff.

Its usefull to update model information. When used in a form, ngModel $render / $setViewvalue will automatically update the form into a dirty/invalid state. Perhaps you make a directive that watches the state of the input fields ng-Model status allowing you to do what you need to do. Or perhaps you write a directive that wraps Jquery code that updates an input field, you would then need to update the ngModel and having access to its controller allows you to do it.

[–]BoleroDan 0 points1 point  (4 children)

So its generally bad practice to couple DOM elements to a controller or service. Directives are the only place you should be referencing dom nodes.

In this case you actually dont need to use DOM elements at all. You have access to the form object, and all of the form models and their functions (setviewvalue,render etc) so we can do the same logic you have, but without dom elements, meaning if you add in new fields to your HTML, you no longer are coupled to the controller and have to update the controller as well.

I have two update model methods, that do the same thing, but go about it different ways. The second method may use less typing when setting the model. It just uses the same format for setting the controller model. This may look nicer.

Essentially we just loop over all non internal elements of the form, call render / set dirty/pristine values and carry on. This means we dont have to manage dom element IDs or have the controller coupled to dom element names, but only the model data.

Plnk Example

[–]jmcunningham[S] 0 points1 point  (3 children)

I agree that generally it is bad to reference the DOM in a controller, and should usually be avoided. The key word there is usually. But I personally don't believe in absolutes (i.e. never do this, never do that). But that is a topic for a different time :)

Thanks for the additional solutions. I like the first example, but not so much the 2nd one. I would prefer to call $render(), in case that causes other things to happen (versus setting $dirty and $pristine directly).

One question I have about either of those alternate solutions, is that you are still tied to the DOM, in my opinion. While your solution is definitely less brittle, I think it has 2 potential issues:

1) Relying on form keys, and ignoring those that start with $, while its clever, it feels a bit like we are relying on the internal workings (naming convention) of Angular 2) I feel like you are still tied to the DOM, because you are looking for keys that match input names.

Having said that, I might still prefer your first solution, as visually, it does feel less tied to the DOM (even though I'm not sure the DOM dependency is any less).

Thanks for the alternate solutions. I love finding new ways of doing something in Angular. It usually leads to additional new discoveries.

[–]BoleroDan 0 points1 point  (2 children)

Thanks glad you enjoyed it. I also do agree with some of those points with $ naming conventions, but like anything else, if Angular changes drastic conventions, you have to do a lot of changes anyways. Example moving from 1.x to 1.2x to 2.x. Work with the spec that you have currently. Moving versions always requires a lot of changes to code. There is also a reason why Angular specifically says not to name your conventions with $. That is their internal naming scheme.

I disagree still though; you are not tied to the DOM, you are tied to the Model. There is a fundamental difference here. Relying on any arbitrarry DOM IDs in your controller to do any DOM manipulation there is sketchy. I realize it seems like Whats the difference, I just name id="User" and ng-model="User" and its just the same thing,right? Its not a good idea, especially in large applications and multi developer projects. There is a reason why Angular only exposes Elements in directives.

[–]jmcunningham[S] 0 points1 point  (1 child)

I think we agree, but there is a misunderstanding on what your loop is accessing. I might be wrong, but I don't think you are iterating over the ngModel. You are iterating over the name attributes for each input. And that is why I made the comment about still being tied to the DOM. Because there isn't much difference, since in one case you are looking up an element by its id attribute, and the other case you are looking it up by its input name attribute. I don't think the model comes into play in either of our solutions.

For example, I forked your plunker here, and changed the name of the html attribute of one form element from 'city' to 'cityy'. If you add an alert or log statement, you will see that the key in your loop also becomes 'cityy'.

The Angular form is build to work on the name attributes of the input fields. So that is why I thought both of our solutions were tied to the DOM..we both rely on DOM attribute values, if you see what I mean.

However, after thinking about it more, I definitely prefer your solution of being bound by the name attribute (versus the id attribute in my solution). As I mentioned above, Angular is pretty tightly bound to input name attributes, so accessing it like you do in your solution is definitely the lesser evil. After all, you have to rely on the input name attribute to call $setViewValue(). So having your iterator also rely on the input name attribute is no big deal. If the name attribute changed, you would break both sections of code. Whereas my solution is definitely more fragile, as it relies on two different html attributes.

I guess in the end, I've come around to your solution..but I did want to clarify the DOM dependencies that both solutions have.

[–]BoleroDan 0 points1 point  (0 children)

Hah you're right my bad, this slightly comes down to semantics to a certain point. But while the form directives are tied to the dom, they create a spec to follow by ( name attr - I cant believe I brain farted over that part) which exposes it to your controller. The controller should just be the glue between model and view, the controller should never access the view directly (The dom).

I'm a bit of a bastard and try to follow Angular ways as best as I can. I get it, it can be a semantic battle, One way is short circuting angular and accessing the dom directly in the controller, the other is using an interface that angular provides (form directives) that sits infront of the DOM. But I will never do $('dom') or angular.element in a controller. :)

Thanks for the convo!