4

This is for an AngularJS app. I have a custom directive that depends on a service.

What I'm really curious about is the "angular way" to deal with a user action that impacts both model and DOM. Some example code:

HTML:

<form foo-places>
    <!--other stuff -->
    <span ng-repeat="place in places">
        <button ng-click="removePlace(place)">remove {{place}}</button>
    </span>        
</form>

JS:

angular.module('foo.directives', []).directive('fooPlaces', 
function(placesService) {
    return {
        controller : function($scope) {
            $scope.places = placesService.places;
                    $scope.removePlace = function(name) {
                placesService.removePlace(name);
            };
            $scope.$on('placesChanged', function() {
                $scope.places = placesService.places;
            });
        },
        link : function($scope, element, attrs) {
                //code to do stuff when user removes a place
        }
    }
})

When a user removes a place (by clicking a button), I also need to do stuff to mess with the DOM, for example, scroll the window to the top, etc. It feels weird to have a function in the controller that deals with the model and then another function in the directive that does the DOM stuff...but both based on the same user action.

Am I over-thinking this or really missing something? How should I handle a single user action that deals with both model and DOM?

BjornJohnson
  • 332
  • 1
  • 8
  • You could have a separate directive to deal with the UI changes which uses `scope.$watch` on your collection of places. That way, you can update UI universally for any change to that collection and just add that directive to the markup element appropriate to the scope you're operating in. I've done this both ways and I think adding a watch is cleaner than mingling model changes with jQuery or other dom manipulation stuff in the link function. – Sounten May 08 '13 at 01:56
  • @Sounten - I don't know if I agree yet that adding a $watch as you've suggested is cleaner. But what I do know is that what I think about angularjs beyond basic examples doesn't always match up with the best way to do real stuff! Thanks for the input. I will play around with that. – BjornJohnson May 08 '13 at 11:42

1 Answers1

4

When you are dealing with AngularJS you might have heard the phrase "The model is the single source of truth". If you understand this part, then the rest of the things fall easily into place. This is the "Angular way".

When the user interacts - he is not interacting with the DOM or the view. He is interacting with the model. The view itself is just a "view" of the model. There could be other views of the same model - which is why the model is the single source of truth. Now, what angular allows you to do is make changes to the model when the user interacts. You make these changes and because the model has changed, the view's start reflecting the changed state of the model.

Also, just to emphasize the separation of concerns - a directive should rarely, deal with a service directly. A directive is a piece of the DOM, which means it is a piece of the view. A service generally has something to do with business logic or represents a model. In MVC or MVVM you dont directly make the View interact with the Model. You always use the ViewModel or Controller in between. This keeps the dependencies to a minimum.

Your ScrollToTop could be a service that you call from your controller (look at $anchorScroll which is a service in Angular ). It doesnt do what you want, but its a scrolling service, which is how you need to implement yours too.

EDIT :

To clarify, you dont generally do DOM manipulately stuff in services. The scenario where you could consider DOM manipulatey stuff in the service, is when, what you are trying to do does not belong to any particular html element, but something that needs to happen on your app level.

Let me explain that. For example, if you are attempting to do something like a dialog / modal window - In angularJS, you would think, the ideal place for something like this is a directive since it is a generic UI component. But if you think about it, a directive in AngularJS is something associated with an element. You always associate a directive with a html element. But as we have seen, a dialog isnt something that you attach to an element, but rather something that is global in nature. This is probably an exception.

The same also holds true to some $window and $document related stuff ( scrolling for example ). These dont belong to any particular element (if you want to scroll inside a div, it should be a directive ), hence they need to be a service. Also, this is a service you could probably inject into a directive. Say each time your directive is triggered you want to scrollToTop or open a dialog. You could inject these kind of services into your directives. The kind of services that you probably should not inject into a directive are services that are associated with business logic. Treat a directive as a re-usable UI component.

Ofcourse, you could create a higher level component ( the stuff you are trying ) which creates a DSL, but then you need to know exactly what you are doing. Until then, I suggest you stick with the plain old controller, directive and services and each managing their own concerns.

ganaraj
  • 26,841
  • 6
  • 63
  • 59
  • Thanks. I think you've done a good job clarifying for me separation of concerns -- especially with your statement "a directive should rarely, deal with a service directly". I have a few follow up questions. What if I wanted to do other things that are "DOM-manipulating-ish" besides scrolling to top? Would you suggest creating services for each one on of those potential things? As you might imagine, a lot of my question is driven by "Only do DOM manipulation in a directive". Does doing stuff related to the DOM in a service violate this? Or am I misunderstanding services now, too? :) – BjornJohnson May 08 '13 at 11:35
  • Excellent clarifications and explanations surrounding app-level/global stuff that just doesn't fit in directives, as well as suggestion not to inject services that deal with business logic, but to allow it when dealing with stuff like dialogs or doc-level scrolling. Thanks so much. – BjornJohnson May 08 '13 at 15:50