4

As I understand, whenever we want to do DOM manipulations in Angular JS for whatever reasons (displaying data, controlling layout behavior, and so on) we should use directives. Now there are times that some DOM manipulation over one element needs info about other DOM elements to act.

One real world example: suppose we want to resize a div to always fit all the screen. So basically the div has one defined height and we want to change this height according to the content and so on to make the div fill the screen. Usually we need to consider other DOM elements like headers and so on when making this kind of manipulation.

Another example is the following: suppose we have a login form, and at the bottom of the form we want a link to allow the user to sign up if he doesn't have an account. When the user clicks the button it should flip the form from login to sign up (usually the other form is coded and hidden). That's fine, if we add a directive on the link to do this it needs to manipulate the form div, and if we put into the form div, it needs to communicate to the link.

In these cases (and I'm really being general, that those are just two possible situations), is there a general guideline on how should we act?

I mean, we have some DOM element <A> and we want to manipulate it somehow, but to do so, we need information about another set of DOM elements <A1>, ... , <An>, so how do we deal with this with consistency?

I've thought on using require and sharing the directive controller. But this is strange, taking again the resizing example we would have <div maintain-height> being maintain-height the directive with the logic to resize this div. Then to communicate with it we would need to put in every other place that triggers a resizing some directive requiring this one. It doesn't seem natural, and in other cases I thought it feels the same.

So, when we have this kind of situation, how can we deal with it maintaing everything well written, maintainable, testable, and etc?

user1620696
  • 10,825
  • 13
  • 60
  • 81

3 Answers3

1

The requirement to do this may indicate an un-angular architecture. Any information you need should be in your view model, either on the current scope or on another scope that you can easily access. You can communicate between scopes by broadcasting (or emitting) messages. You could also put the data you require in a service and inject that into the controller wherever you need it.

If for some reason you must access other elements and read data directly from them you can do this in the normal jQuery / jQLite fashion within a directive. There is also the $document service.

The particular use case you describe seems strange. Why not just use CSS to handle re-sizing DOM elements?

Your other use-case (changing a form) would be handled by a scope variable such as showLogin. The two different forms are hidden or shown according to the value of that variable. Or am I missing something about your requirement?

dave walker
  • 3,058
  • 1
  • 24
  • 30
  • Thanks for the info @david004. Isn't it a bad practice to reference another element from within a directive? I mean, I would be coupling them. Also, this use case I took from a jQuery theme I'm converting to angular. But basically it's the "min-height". The div must stretch a certain size or some bugs occur. So when content changes, window is resized or dropdown menu's are opened the function runs and sets the min-height again. I think it wouldn't be possible to do this with CSS only. – user1620696 Jun 02 '14 at 23:59
  • If you are converting a jQuery theme I guess there is a possibility that you are approaching the problem from a jQuery perspective. Have you read [this very popular post](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background/15012542#15012542). Regarding bad practice, it may not be ideal, but probably not a disaster to access other parts of the DOM from within a directive, particularly if you provide some logic to handle the case of those elements not being present. – dave walker Jun 03 '14 at 00:07
  • @user1620696, it is not bad practice to reference one directive from another, especially if you use a controller or service as a mediator. I agree about the "thinking in jQuery" theme in the previous comment. – trysis Jun 03 '14 at 00:16
  • @david004, this post helped clarifying some things. Now, just one more thing: in the resizing case, the resizing is done relative to content (that's why not just CSS). I've found out the best way to have a "resize-min-height" directive on the div being resized. Now, this resizing behavior must be triggered by other directives. How can this be done? I mean, I think requiring cannot be done, because the other directives are placed on other elements. – user1620696 Jun 04 '14 at 01:35
  • It may be surprising to know that there is no simple solution to your problem. You could use the relatively new `MutationObserver`. [Here](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver). Or you can bind the value of the height and width classes to the view model, then watch them and transmit a message when they change. – dave walker Jun 04 '14 at 04:00
0

All DOM manipulation should always always always be done in directives in AngularJS. To share non-DOM data between directives, use controllers and/or services.

In your first use case, I would use something like Twitter-Bootstrap to do responsive stuff. Bootstrap is a CSS framework, though it has JavaScript components in it. One of the CSS components is resizing elements to make them look good on certain screen sizes. Most classes I've seen that deal with screen size have 4 sizes: tiny screens, like smartphone screens, tablet-sized screens, small-to-medium-sized computers, and larger computers. These sizes seem to work except perhaps for sizes directly in between, which I assume are not widely implemented. These classes usually go on container elements, such as <div>s, though there are others which are meant to be attached to headers, etc.

As for your second use case, logging in is always a challenge. I'm not sure how to solve your specific case, as I have not needed to make sites extremely secure, but I would start by defining a variable for logging in in a controller, and use this to decide what login state to put the page in. The variable would make the directive which corresponds to this login state visible, and the others invisible. When the user pressed the link, it would change this variable, which would in turn change the visible directive. I know I said to always do DOM stuff in directives, but this is an edge case, and deals with scopes, which are a controller's purview.

trysis
  • 8,086
  • 17
  • 51
  • 80
0

The trouble with DOM dependencies within a directive is that the DOM can mutate and change as directives are compiled and linked.

As long as directives play by the rules, however, you can rely on a few things:

  1. During the compilation phase, elements are compiled by walking the DOM tree from parent to child starting from the outer-most element to the inner-most element and calling their 'compile' functions in priority order (highest to lowest).
  2. A directive's compile function should only modify it's children (not its parents or grandparents). The reason is that child elements have not been compiled yet so its safe to mutate them, the angular compiler will eventually walk the DOM tree and compile the modified DOM. Parent elements have already been "walked" and the opportunity to compile parent elements has come and gone. If a directive mutated its parents, it would require re-compilation, which is usually not a good idea (infinite recursion).
  3. During the (post) linking phase, elements are linked in reverse order that they are compiled. This means that when a directive is being linked, its contents (or children) have already been linked and you can rely on the DOM tree of the children. Parent elements however, have not been linked yet.

So knowing this how could you design a directive that has external DOM dependencies? Well, you would need to know the final state of the DOM after compilation and linking is done. For that, I would recommend at least two directives:

  1. A master directive that is placed at highest level where you can expose controller functions that can be called to get external DOM data (based on the current compiled and linked state of its DOM tree).
  2. A child directive that is nested somewhere within the master directive that can be notified when the master directive has completed its final Linking phase.

Unfortunately, that does not guarantee that $watch handlers (or even a parent linking function) will modify the DOM in unexpected ways. If you know what directives are at play, you should be able to get this to work reliably.

Michael Kang
  • 52,003
  • 16
  • 103
  • 135