4

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?

flapas
  • 583
  • 1
  • 11
  • 25
  • can you try $scope.sidebar instead of ctrlModel.sidebar in controller? Similarly for pages as well.. – ABOS Apr 08 '15 at 16:34
  • Just tried it, nothing changed – flapas Apr 08 '15 at 17:08
  • In controller, you need to use $scope.sidebar = {};$scope.sidebar.pages = data; and your directive should be "mmDirective" consistent with your html. This worked for me. Also change page:'@' to '=', remove {{}} in your html correspondingly (might not needed) – ABOS Apr 08 '15 at 17:26
  • Yeah! You were right all along! I forgot to change the HTML, so I still had the `ng-controller = '... as ...' ` and the wrong references. The $watch is now called twice, as it's supposed and the data is there on the second call. However, I went with the other solutions, because they maintained the `this` approach, which I like best – flapas Apr 08 '15 at 21:44

2 Answers2

2

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

Jasen
  • 14,030
  • 3
  • 51
  • 68
1

The problem appears to be your html markup.

In your controller you have specified the ctrlModel is equal to this.

In your html markup you have declared the same this to be named interactionsController.
So tacking on ctrlModel to interactionsController is incorrect.

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

    <!-- replace with this -->
    <mm-sidebar pages='{{interactionsCtrl.sidebar.pages}}'>

    </mm-sidebar>
  </div>
</body>
bluetoft
  • 5,373
  • 2
  • 23
  • 26