0

I am working on a small piece of code which is being handled inside of a directive. The directive is passed a large object of data that's separated into types and subtypes. Using drop downs the user is meant to be able to select which part of the data they want to see in the display, and it should be dynamic, meaning that as they change their selection, the data would immediately update. My first attempt to do this was to put code into a controller attached to the directive.

Using a controller attached to the directive

View

<select ng-model="type" ng-change="updateDisplay()">
    <option value="typeOne">One</option>
    <option value="typeTwo">Two</option>
    <option value="typeThree">Three</option>
</select>

<select ng-model="subType" ng-change="updateDisplay()">
    <option value="subTypeOne">One</option>
    <option value="subTypeTwo">Two</option>
    <option value="subTypeThree">Three</option>
</select>

...

<div ng-repeat="(key, value) in selectedData">
     {{key}}, {{value}}
</div>

Controller

$scope.type = 'typeOne';
$scope.subType = 'subTypeOne';
$scope.selectedData = null;

$scope.updateDisplay = function() {
   //$scope.data is a large object of data passed into the directive, but not important to define for this post 
   $scope.selectedData = $scope.data[$scope.type][$scope.subType];
}

$scope.updateDisplay();  // call on page load to populate with defaults

This approach works fine, but it has been suggested to me that this is not the proper "angular" way and that all of this should be deferred to a service. In this case, not only would the data need to be passed to my directive, but all of the service methods as well. On the select drop downs, we would need to put a ng-change call to let the service know that the value has changed and needs to be updated

ng-change="setSelectedType({type:type})"

and then another function presumably would need to run after that "Setter" function has run, in order to recalculate the dynamic value for selectedData. (an equivalant of running updateDisplay() above.

My overall question is if moving all this logic to a service even makes sense? I would have to write getters and setters on the service side and call them each time on ng-change in addition to having the service then return the filtered data. Conversely if it is done in a controller we don't need to create any "setters/getters" for the variables, because they are bound to the scope and update automatically. (and to me the whole point of using a framework like angular).

Without making this question TOO opinionated, can someone with Angular experience talk about this?

mls3590712
  • 750
  • 1
  • 6
  • 20
  • 2
    As it seems that doing things the right way is important to you, I would highly recommend to use `controller as` syntax instead of injeting `$scope`. [It is a lot cleaner, easier to read and safer for the future](http://stackoverflow.com/questions/32755929/what-is-the-advantage-of-controller-as-in-angular/32756679#32756679) – LionC Sep 24 '15 at 14:16
  • Yes, good point @LionC – mls3590712 Sep 24 '15 at 14:18

2 Answers2

0

Personally I don't believe in hard and fast rules for development but, in general, I think most developers would agree:

  • You should have thin (minimal) controllers, if at all
  • Your business logic should be in services
  • Directives should only be for DOM manipulation

Put it this way: what are you going to do if you decide you want to re-use your directive on a different page or in a different controller? Now you have to re-write all the code that was in your first controller.

Whereas if you have used a service then you just inject the directive and service into your new controller (and link the two via the controller) - job done!

One final thing: you want to limit the interaction between the directive and the controller to well defined interface. Use the 'scope' property in the directive to do that.

  • I appreciate your response. Can you add some detail to your answer? What do you mean by "business logic"? Would in my example "updateDisplay()" be considered such? Also I am not sure why I would ever have to re-write my code if I wanted to use my directive in another page. The directive is defined with a templateUrl, a controller unique to the directive, and a scope{} which allows the big dataobject to be passed to it. Wouldn't I just be able to use the directive again on any other page? – mls3590712 Sep 24 '15 at 14:46
  • No problem! You described business logic with the term 'logic' in your question: getters / setters, data manipulation, etc. If this code was in a controller you would have to re-write it if the directive was tied to another controller. If the code was in a service, you wouldn't. – James Drummond Sep 24 '15 at 15:26
0

I don't see any logic in your example. I do agree that business logic is best located in a service, because it helps to black box that logic from the display aspect of the app.

I think the angular way would be to use watchers.

scope.$watch('subType',function(subType) {
   scope.selectedData = data[scope.type || 'defaultKey'][subType || 'defaultKey'];
});

There is no need to have an updateData function or call it on page load, because the watchers will execute at least once during the first digest.

The other issue is scope.data and if that really needs to be on the scope. If there is nothing in the template directly binding to it, then keep it out of the scope. This helps keep the scope related to what's in the template. Later when you debug in the browser and look at the scope $($0).scope() you will only see what's important.

If you had data in the scope. You could just do this. Angular will not error on missing keys. The template will just no repeat unless the selections are valid.

<div ng-repeat="(key, value) in data[type][subType]">
    {{key}}, {{value}}
</div>
Reactgular
  • 52,335
  • 19
  • 158
  • 208