Search code examples
angularjsangularjs-directiveangularjs-scopeangularjs-service

Passing asynchronously obtained data to a directive


I currently have an AngularJS controller that is basically getting some JSON asynchronously through a $http.get() call, then linking the obtained data to some scope variable.

A resumed version of the controller code:

mapsControllers.controller('interactionsController', ['$http', function($http) {
  var ctrlModel = this;

  $http.get("data/interactionsPages.json").
  success(function(data) {
    ctrlModel.sidebar = {};
    ctrlModel.sidebar.pages = data;
  }).
  error(function() {...});
}]);

Then, I have a custom directive which receives those same scope variables through a HTML element.

A resumed version of the directive code:

mapsDirectives.directive('sidebar', function() {
  return {
    restrict : 'E',
    scope : {
      pages : '@'
    },
    controller : function($scope) {            
      $scope.firstPage = 0;

      $scope.lastPage = $scope.pages.length - 1;

      $scope.activePage = 0;

      //...
    },
    link : function(scope) {
      console.log(scope.pages);
    },
    templateURL : 'sidebar.html'
  }
});

A resumed version of the HTML:

<body>
  <div ng-controller='interactionsController as interactionsCtrl'>
    <mm-sidebar pages='{{interactionsCtrl.ctrlModel.sidebar.pages}}'>
    </mm-sidebar>
  </div>
</body>

The problem is, since the $http.get() is asynchronous, the directive is being badly initialised (e.g: $scope.pages.length - 1 is undefined).

I couldn't find anything that solved this problem for me, although there are some presented solutions that would seem to solve the case. Namely, I tried to watch the variables, only initialising the variables after detected changes, as suggested in many other posts. For testing, I used something like:

//... inside the directive's return{ }
link: function() {
  scope.$watch('pages', function(pages){
    if(pages)
      console.log(pages);
  });
}

I've tested it, and the $watch function wasn't called more than once (the logged value being undefined), which, I assume, means it isn't detecting the change in the variable value. However, I confirmed that the value was being changed.

So, what is the problem here?


Solution

  • Move the declaration for the sidebar object in the controller and change the scope binding to =.

    mapsDirectives.controller("interactionsController", ["$http", "$timeout",
        function($http, $timeout) {
            var ctrlModel = this;
            ctrlModel.sidebar = {
                pages: []
            };
          /*
          $http.get("data/interactionsPages.json").
              success(function(data) {
              //ctrlModel.sidebar = {};
              ctrlModel.sidebar.pages = data;
           }).
           error(function() {});
          */
    
          $timeout(function() {
            //ctrlModel.sidebar = {};
            ctrlModel.sidebar.pages = ["one", "two"];
          }, 2000);
        }
    ]);
    
    mapsDirectives.directive('mmSidebar', [function() {
        return {
          restrict: 'E',
          scope: {
            pages: '='
          },
          controller: function() {},
          link: function(scope, element, attrs, ctrl) {
            scope.$watch("pages", function(val) {
              scope.firstPage = 0;
              scope.lastPage = scope.pages.length - 1;
              scope.activePage = 0;
            });
          },
          templateUrl: 'sidebar.html'
        };
    }]);
    

    Then match the directive name and drop the braces.

    <mm-sidebar pages='interactionsCtrl.sidebar.pages'>
    </mm-sidebar>
    

    Here's a working example: http://plnkr.co/edit/VP79w4vL5xiifEWqAUGI