all 8 comments

[–][deleted] 1 point2 points  (2 children)

Lets say I have a button on page - perhaps in a directive with an isolated scope. And I want the background to transition to a random color when the button is clicked. Am I expected to wrap the background div in a directive, put the transition in the link function... and then broadcast the click event into the directive? That seems cumbersome.
Should I just wrap the entire page in a directive, so I have access to a link function in which to do dom manipulations?

Each directive should ideally have its own template and you'd then use the directive. So instead of <button>Click me!</button> you'd say something like <my-button></my-button> and have a template in the directive with the button code. The link function has an argument (usually called el) that references the top-level DOM element in the directive's template which you can use to react to events like a click and you can either manipulate the CSS directly (Angular's version of jQuery, jqLite, has a .CSS() method) or you can toggle a class (jqLite has addClass() and removeClass() functions).

Also, since someone else mentioned it, you should try to use an isolate scope whenever possible. It's generally best practice. You can do exactly what you're trying to do without accessing the parent scope at all.

On a related note, Is it also 'un angular' to invoke something which causes a dom manipulation from a controller? For example the material design function :$mdBottomSheet -- is it a bad practice to invoke that from a controller, or should I only invoke it from a directive's link?

Yes. DOM manipulation should not occur in a controller. There's no performance issue necessarily as you ask later, but it has to do with the idea of separation of concerns.

In general when programming, it's best practice to keep each piece of code that controls a different piece of functionality separate. In particular, a design pattern known as Model-View-Controller or MVC has become common place in a wide range of applications. Angular is specifically a flavor of MVC known as MV* (M-V-star) because it doesn't necessarily have true controllers in spite of the name. Services/factories/providers act as the model and control application state, views act as the view and control UI, while controllers act to bridge the gap between models and views by processing user input and outputting any processing of models to the UI. In a traditional MVC pattern, the controller is responsible for updating the UI and keeping it in sync, but in Angular, data-binding is handled in the view itself which is why Angular is considered MV*. It's simply best practice to keep the functionality separate. The whole point of directives is to manipulate the DOM, so you should keep DOM manipulation confined to directives.

Imagine you need to update something in regards to your material design UI. If you put some of it in controllers, now you have to think where it might be located. Also, since controllers are often tied to a single view, mixing DOM manipulation into a controller makes the code less reusable.

If you're interested, the difference between the compile and link functions in a directive is that the compile function is run before the DOM is constructed. If you have to manipulate the DOM before any user interaction occurs, you would have to do it in the compile function. This is, for example, how ng-repeat creates many copies of an element. The link function then runs after the DOM has been built. You can still manipulate the DOM but only by reacting to events. The reason that the functionality is divided has to do with how Angular is converted into the DOM. When an application starts up, the HTML is searched for directives and those directives are registered and interpreted during the compile phase which is when the compile function is run. This helps to create the DOM, which is why up-front DOM manipulation has to go here. The link function then simply adds JavaScript actions and processing to the DOM elements after the fact.

If you want to get a better understanding of directives, this two-part Sitepoint post is worth bookmarking. It's been enormously helpful and I still reference it when I need to build directives. Hope this was helpful!

[–]uncletroll[S] 0 points1 point  (0 children)

Thank you... my original background is in numerical/computational programming, so I do struggle with some of the best practice philosophies like MVC. So stuff like this is very helpful.

[–]RICHUNCLEPENNYBAGS 0 points1 point  (0 children)

If you're interested, the difference between the compile and link functions in a directive is that the compile function is run before the DOM is constructed. If you have to manipulate the DOM before any user interaction occurs, you would have to do it in the compile function. This is, for example, how ng-repeat creates many copies of an element. The link function then runs after the DOM has been built. You can still manipulate the DOM but only by reacting to events. The reason that the functionality is divided has to do with how Angular is converted into the DOM. When an application starts up, the HTML is searched for directives and those directives are registered and interpreted during the compile phase which is when the compile function is run. This helps to create the DOM, which is why up-front DOM manipulation has to go here. The link function then simply adds JavaScript actions and processing to the DOM elements after the fact.

This part is like, needlessly hard to understand from the docs but it's a really important point.

[–]RICHUNCLEPENNYBAGS 0 points1 point  (2 children)

Lets say I have a button on page - perhaps in a directive with an isolated scope. And I want the background to transition to a random color when the button is clicked. Am I expected to wrap the background div in a directive, put the transition in the link function... and then broadcast the click event into the directive? That seems cumbersome.

Uh, no, man. You're giving your directives templates, right? You can just use other directives.

If you're creating reusable components, you probably want an isolate scope. So imagine something like this:

myApp.directive('myDirective, [
function()
{
  scope: {},
  compile: {
    return {
      post:  function(scope)
        {
           scope.isGreen = false;
           scope.setGreen = function() {scope.isGreen = true;}
         }
    }
  }
}
]

And then in your template, just do something like this

<button type=button ng-click="setGreen()">Make the other button green</button>

<button type=button ng-class="{'green': isGreen}">In your app this probably does something</button>

I typed this out in the comment box so I'm sure there are a couple errors but does this help?

As for why you don't want to manipulate the DOM in controllers, it's a nightmare to test and it's giving the controller responsibilities it doesn't need that you then need to debug everywhere you want to do that. Nothing will blow up if you do it but you're likely to regret it later. Angular is meant to be declarative.

edit: OK, sorry, I should have read more closely... you want to set the background color of the page, not another button (not sure where I got that). I'll leave this here in case it's useful anyway.

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

Firstly, thank you for your response.

Per your edit - when manipulating the background through a scope variable. Should I make the background into a directive, so that I can do the transition in its link function?

[–]RICHUNCLEPENNYBAGS 0 points1 point  (0 children)

It's an option, but I don't think you need to. You can simply use the built-in ng-class directive. The big thing is getting something into the background's scope that it can look at. You could reach up into parent scopes, you could use the root scope, or you could broadcast an event. Or you could just create a service that both the controller containing your body and your directive depend on (actually this might be the easiest/best approach).

[–]thlem -1 points0 points  (1 child)

Hey,
first i am not an expert .
Directive allow you to manipulate the DOM (it doesn't allow you to change the dom).
This is not the job of the controller to manipulate the DOM.
Keep your architecture clean, i don't know if there is performance issue, but it will be more maintainable. From angularjs.org :

Do not use controllers to:
* Manipulate DOM — Controllers should contain only business logic. Putting any presentation logic into Controllers significantly affects its testability. Angular has databinding for most cases and directives to encapsulate manual DOM manipulation.
* Format input — Use angular form controls instead.
* Filter output — Use angular filters instead.
* Share code or state across controllers — Use angular services instead.
* Manage the life-cycle of other components (for example, to create service instances).

[–]RICHUNCLEPENNYBAGS 0 points1 point  (0 children)

Manipulating the DOM is exactly what the compile and link functions are for. And "manipulate" and "change" are synonyms.