DOM manipulation in AngularJS services

12,049

Solution 1

A directive, with the way it is defined, is always attached to a DOM node. So when you define a directive, it "expands" or replaces the DOM node to which it is attached.

In certain situations (like dialogs) you won't be able to attach DOM nodes to any specific parent. In these cases using a service makes sense and the controller can still stay out of the DOM bit because the DOM manipulation will be encapsulated in a service..

Popups could be another situation where we could probably use a service, but unlike a dialog, a popup IS attached to a DOM node. So, even that is slightly a grey area.

So, a basic and simple test is, "Can this bit of DOM Manipulation code be attached to a DOM node?" If yes, then directive. If no, then service.

Dialogs and Custom Confirm Boxes come in as typical examples where you would use a service.

Solution 2

Whilst I think Ganaraj has described what Misko was saying well, you could (and potentially should) argue that the DOM manipulation code for a modal dialogue (for example) can be put into a DOM node.

One approach is to have a dialog directive attached to the DOM the whole time, and then use ng-show to conditionally show it. You can then communicate with the modal dialog using either $rootScope, or better: a service.

I actually prefer this approach because it feels 'right' - the service handles the transfer of data and the directives interact with the service to make sure it gets displayed in a way that makes sense to the user.

But I think the fact that Misko is not particularly clear about it shows that it's quite subjective. Do what makes the most sense to you.

Solution 3

I'm googling this topic to reinforce my inner feeling about this because I'm working on an issue right now that makes me want to place certain logic in a service. Here's my case, and what I think is a plenty good justification for putting dom-based handling in service:

I have directive-based elements that react to mouse position, globally (e.g. they move or change in some way based off mouse position). There are an indeterminate number of these elements and they don't pertain to any specific location in the application (since it's a GUI element and can pertain to any container anywhere). If I were to adhere to the strict angular "dom logic in the directives only" rule, it'd be less efficient because the elements all share the logic pertaining to parsing the mouse position (efficiently) which revolves around window.requestAnimationFrame ticks.

Were I to bundle that logic into the directive, I'd have a listener/raf loop tied to every single instance. While it'd still be DRY, it wouldn't be efficient since on every move of the mouse you'd be firing the exact same listener that would return the exact same result for every single element.

It's actually best in this case to move this into a service, despite it being dom-based logic, and register each directive instance against the service to call the same, instance-based logic against the logic performed that would be duplicate for each instance.

Remember, while Angular provides some very good advice around how to structure you code, that does not make it bullet proof of by any means a hard and fast rule, since it can't possibly cover all use cases. If you see a hole where the "best practices" seem to fail, its because you're actually properly understanding the best practices, and you've now found a reason to break the rules on purpose.

That's just my 2 cents!!

Solution 4

I agree with @dudewad. At the end of the day a service (factory, provider, value) is just angular's module pattern with the limitation of being implemented as a singleton. I think it's important that you get access to the DOM via an element that is passed into a directive's link function and not by using document or other global approaches. However, I don't think that it's important that the logic that you apply to dom element that you get from a directive lives in the same module. For SRP reasons it can be favorable to break up the code a bit using a service as you might have a particularly complex piece of logic that it makes more sense to have focused test around or you might want to use the logic in more than one directive as pointed out by @dudewad.

Solution 5

one drawback to using a DOM manipulation method based off of changing a variable (ie, ng-show="isVisible") is that the DOM manipulation occurs after the next "javascript turn" loop (when isVisible is updated). You may need the DOM to be updated right away.

For example, a common scenario is displaying a "spinner" during transitions to a new route / state. If you were to set $scope.isVisible = true on the $routeChangeStart / $stateChangeStart event, and then $scope.isVisible = false on the $routeChangeSuccess / $stateChangeSuccess event, you will never see your ng-show, as the whole route / state change happens within one javascript turn. It would be better to use .show() and .hide() in those events so that you actually see the spinner.

to bring this all back and make it relevant to the OP's question -- in a situation where the DOM manipulation is a "spinner" modal being displayed, I would do it during the service, and I would do it with direct DOM manipulation methods, rather than relying on a model change.

Share:
12,049
Florian F
Author by

Florian F

Work @Kap_IT

Updated on June 07, 2022

Comments

  • Florian F
    Florian F about 2 years

    It's well known that you must manipulate DOM elements inside directives when using AngularJS.

    However, it seems that, in some use cases, manipulating DOM inside a service is acceptable. Misko Hevery is talking about this here. You can also find an example within the Bootstrap UI Dialog.

    Misko's explanation is rather vague so I was wondering how do you determine when you need to put DOM inside a service instead of a directive.