9

Let's say I'm loading a variable into $scope with $http:

$http.get('/teachers/4').success(function(data){
  $scope.teacher = data;
});

My template uses this data:

Teacher: {{teacher.name}}
<students-view students="teacher.students"></students-view>

This directive can load BEFORE teacher finishes loading, but my directive has code that depends on the teacher.students array being loaded:

app.directive('studentsView', function(){
  return {
    scope: { students: '=' },
    controller: function($scope){
      _.each($scope.students, function(s){
        // this is not called if teacher loads after this directive
      });
    }
  };
});

How do I get the behavior that I want here? I don't want to stop using $http, and I would like to not have to assign a promise to the scope if possible.

Dave
  • 2,735
  • 8
  • 40
  • 44
  • what is happening in the loop? Can put that loop inside the success callback in controller if that helps or move the whole request to a service – charlietfl Dec 19 '14 at 00:00

2 Answers2

21

Use a watch to wait for students to be available. Once it's available you call the code that depends on it, then remove the watch. You can skip removing the watch if you want the code to execute every time students changes.

app.directive('studentsView', function(){
  return {
    scope: { students: '=' },
    link: function($scope){
      var unwatch = $scope.$watch('students', function(newVal, oldVal){
        // or $watchCollection if students is an array
        if (newVal) {
          init();
          // remove the watcher
          unwatch();
        }
      });

      function init(){
        _.each($scope.students, function(s){
          // do stuff
        });
      }
    }
  };
});
user2943490
  • 6,900
  • 2
  • 22
  • 38
  • What's the difference between putting this code in the controller function, and putting it into the link function? – Dave Dec 19 '14 at 10:52
  • 2
    @Dave In the DOM compilation process, directive controllers get instantiated before link functions, so the link functions can essentially be seen acting on the end/stable state. The Angular docs recommend using [_controller when you want to expose an API to other directives. Otherwise use link](https://docs.angularjs.org/guide/directive#summary)_. Some argue otherwise: [Difference between the 'controller', 'link' and 'compile' functions when defining a directive](http://stackoverflow.com/a/12570008/2943490). It just depends on whether the aforementioned ordering matters to your use case. – user2943490 Dec 19 '14 at 11:25
3

You'll probably need to do some kind of watch on students to know when it is updated, then run your _.each when the watch is triggered:

app.directive('studentsView', function(){
  return {
    scope: { students: '=' },
    controller: function($scope){
      scope.$watch('students', function(newValue, oldValue) {
        _.each($scope.students, function(s){
          // this is not called if teacher loads after this directive
        });     
      };
    }
  };
});

More on $watch: https://docs.angularjs.org/api/ng/type/$rootScope.Scope

jdmcnair
  • 1,305
  • 15
  • 33