4

I have a HTML partial that I'm trying to bind AJAX data too. For some reason, it seems I must put the AJAX service call within the directive's link function. I cannot just put it in the controller; I do it in the directive like this:

link: function(scope){
  MyService.getData().then(function(res){
    scope.myData = res.data;
  }, function(res){
    throw new Error('Error getting data');
  });
}

First of all, I'm curious why I cannot do this in my controller. But also, There is some manipulation I want to do to the data once I get it. Does that all need to happen in the linked function. In the controller looping over $scope.myData won't work because it isn't defined yet? Can I use my AJAX data in my controller somehow without having to do my logic in a linked file?

1252748
  • 14,597
  • 32
  • 109
  • 229
  • You should manipulate the data in the callback function of your service. Basically, before you set the scope.myData. There should be no problem doing this in the controller, so I'd suggest that you put it there as the link function is used for different reasons. – Swimburger Mar 25 '16 at 04:14

5 Answers5

7

The task can be done in various ways but the question is that which practice is the best. First of all if you have a directive with partial and you want to bind to it, you can do it in the directive's controller

app.directive('myPartial', function() {
  return {
    restrict:'AE',
    template:'<pre>[{{myData| json}}]</pre>',
    scope:true,   
    link:function(scope, elem, attrs) {
      /*avoid using model manipulation in link function as it is mostly used 

      for DOM manipulation such as angular.element(elem).addClass('myTemplateClass');*/
    },
    controller: function($scope,MyService){
         MyService.getData().then(function(dataFromServer){
             scope.myData = dataFromServer;
         }, function(err){
            throw new Error('Error getting data : ' + err);
         });
    }
  };
});

You have to have THIN controllers and THICK services when working with AngularJS.. if you have to manipulate data. Do it in the service and return such form of data to controller which is directly assignable to the controller. That's the best practice. I.e. in your service. Do this

app.factory('MyService',function($http,$q){

     var _modifyGetData = function(serverData){
        // modify your data here...
        // for example -> serverData.usersCount = serverData.users.length;
        return serverData;
     };
     var getData = function(){
         var dfd = $q.defer();
         $http.get(/*Some url here*/).then(function(res){
              var data = res.data;
              // manipulate all your data using a function maybe
              data = _modifyGetData(data);
              dfd.resolve(data);   //resolve the modified data


         });
         return dfd.promise;
     }
     return {
        getData:getData
     };
});

Hope this helps.

Some of the online sources that might help: https://scotch.io/tutorials/making-skinny-angularjs-controllers https://teamgaslight.com/blog/4-lessons-learned-doing-angular-on-rails

Muhammad Ahsan Ayaz
  • 1,867
  • 10
  • 12
0

How're you putting it in your controller? I have a function in my controller that I call via ng-init, to load up my data. Example based on your snippet:

.controller('myCtrl', function ($scope, MyService) {
        $scope.initData = function(){
            MyService.getData().then(function(res){
                $scope.myData = res.data;
              }, function(res){
                throw new Error('Error getting data');
              });
        }
}

Then in my partial, in one of the HTML tags I have:

<div ng-init="initData()"></div>

Just add the ng-init to one of your HTML tags to send the call. Not sure if it's the right way, but it works for my scenarios, and should work for yours too.

Tiago
  • 1,984
  • 1
  • 21
  • 43
  • I think it's better to just invoke the function inside the controller immediately, instead of putting it in the ng-init. If you put in ng-init it will be invoked the moment that div gets rendered, but I don't see that being helpful. – Swimburger Mar 25 '16 at 04:05
  • @sniels thanks for the input, your reasoning makes sense. I'll need to try that approach myself. – Tiago Mar 25 '16 at 17:05
0

There are obviously several ways to accomplish this; some may be better than others depending on your needs. I suggest thinking more about the data that you are getting from the service and where it belongs.

  1. If that data is only maintained by the directive, then you should continue keeping your code the way it is in the link function. You encapsulate the data and its manipulation inside the directive. (obviously this option won't work for your case, but I'm just explaining when this might make sense)
  2. If that data needs to be accessed only by your single controller, maybe expose a scope binding from your directive, be it one-way or two-way to fit your needs (look at "Isolating the Scope of a Directive" on this page). This way you can get the data in the controller and pass it into the directive for it to "operate" on.
  3. If that data is something that several directives and controllers are going to need access to, it should exist completely in the service itself. In this way, it doesn't matter if if a controller or a directive accesses the data because they all see the same data. Obviously your question is about ajax calls, so you would need to establish a caching mechanism for your service (check out this).

Either way, you should probably use (3) above anyways for your service(s). Just be aware of gotchas when dealing with a local cache. Hope this helps.

EDIT: If you're looking for coding examples, please let me know -- your question seemed more about "what are my options" and not specifically about technical implementation details.

Cheers!

Community
  • 1
  • 1
Brent McFerrin
  • 528
  • 4
  • 9
0

You can call the service from your controller.

What was the reason that you could not?

YourController.$inject = ['MyService', '$scope'];
function YourController(myService, $scope) {

    myService.getData().then(
        function(res) { 
            $scope.myData = res.data;
            // or manipulate the data
            $scope.apply(); 
        })
}
Martin
  • 15,820
  • 4
  • 47
  • 56
0

You can do it in the directive's controller, such as:

app.directive('output', function() {
  return {
    retrict:'E',
    replace:true,
    template:'<pre>[{{MyData | json}}]</pre>',
    scope:true,
    controller:function(MyService, $scope) {
      $scope.MyData = $scope.MyData || {};
      MyService.getData().then(function(value){
        $scope.MyData.value = value;
      });
    },
    link:function(scope, element, attrs) {
      scope.MyData = scope.MyData || {};
    }
  };
});

See this fiddle: http://jsfiddle.net/oq2m149e

malix
  • 3,566
  • 1
  • 31
  • 41
  • 2
    I think the link function is useless in this case, I'd just remove the link function and the check in the controller if there's already a value for $scope.myData. – Swimburger Mar 25 '16 at 04:09
  • You're also using the scope option wrong, and should probably just remove it. Other than that, it seems a good answer. – Swimburger Mar 25 '16 at 04:21
  • @sniels you are right about link: superfluous... You are wrong about scope: https://github.com/angular/angular.js/wiki/Understanding-Scopes – malix Mar 25 '16 at 06:17
  • 1
    @malix I don't think he is wrong, controller and link has the same scope, even if isolated. The difference is that controller is executed pre-render and link is done post-render so in link function you are suppose to do all the DOM manipulation. Check my plunker http://plnkr.co/edit/5wauNl5iAruFwztlrDy3?p=preview – maurycy Mar 27 '16 at 11:22
  • @maurycy I know... I was replying to "You're also using the scope option wrong, and should probably just remove it." Whereas `scope:true` is clearly allowed in the docs... – malix Mar 28 '16 at 02:52