1

Basically, I have two directives:

angular.module("maineMap")
.directive("ngMap", ["APPCONFIG", "Map", function(config, Map){
       //D3 map drawing functionality based on data from $resource call
       return {
        restrict : "E",
        transclude : true,
        scope : {
            mapData : '='
        },
        link : function(scope, el, attrs){
                   }
        };
    }])
    .directive("donutChart", function(){
    return {
        restrict : "E",
        link : function(scope, el, attrs){
                    }
            }
    });

and a controller

angular.module("maineMap")
.controller('MapCtrl', ['$scope','Map', function($scope, Map{
    $scope.mapData = Map.mapData()  
                        .query();

    $scope.mapData.$promise.then(function(results){
        $scope.mapData = results;
        console.log($scope.mapData);
    });

}]);

where Map is a $resource implementation to get a JSON file.

My issue is that the directive functionality is executing prior to the controller call. That is, I call upon several properties of mapData within the directives, and they are all returning undefined with corresponding error messages in the console. However, shortly after the error printout, the data fetch from the $resource implementation executes and is printed to console.

Note that if I replace the $promise and depend solely on

$scope.mapData = Map.mapData()  
                        .query();

then I have $scope.mapData visible in the <donut-chart> directive, but not <ng-map>.

Given this kind of structure, how can I delay the directive functionality until after the controller loads the data?

Jason
  • 11,263
  • 21
  • 87
  • 181

2 Answers2

0

The order of execution for:

<div ng-controller="controller">
    <outer>
      <inner></inner>
      <inner></inner>
    </outer>
</div>

would be:

  1. compile (inner)
  2. compile (inner)
  3. compile (outer)
  4. controller
  5. controller (outer)
  6. controller (inner)
  7. link (inner)
  8. controller (inner)
  9. link (inner)
  10. link (outer)

So, controller clearly sets the scope before linking of directives occurs. Unless, of course, the scope is set in an asynchronous function. But your directive should be able to handle undefined scope values since the directive doesn't (an probably shouldn't) care under which controller it's operating under unless it requires it.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • Thats the thing.. the scope value is set in an asynchronous function and the data fetched is integral to the directive. If the directive doesn't have the data, it throws errors and doesn't do anything. – Jason Oct 03 '14 at 21:30
  • That doesn't look like a good design. A directive should be able to handle `undefined` values and should not make any assumptions about what the parent scope has or doesn't have. – New Dev Oct 03 '14 at 22:29
  • Its a D3 app, which requires data to draw the svg paths. – Jason Oct 03 '14 at 22:30
  • @Jason, how does that change anything? – New Dev Oct 06 '14 at 20:44
0

I used joakimbi's solution for this problem. Specifically, I added a ngRoute dependency and use the resolve property to ensure that the promises enclosed would execute a data fetch prior to displaying a view.

Updated service:

angular.module("maineMap")
.factory("MapService", ["$http", "$log", "$q", "appConfig", function($http, $log, $q, appConfig){
    var mapPaths, mapPromise, 
        cityPositions, cityPromise;

    return {
        getMapPaths : function(){

            mapPromise = $http.get(appConfig.maineData)
                                .success(function(data){
                                    mapPaths = data;
                                });

            return {
                getData : function(){
                    return mapPaths;
                },
                setData : function(data){
                    mapPaths = data;
                },
                promise : mapPromise
            };
        },
        getCityPositions : function() { ... }
   };
}]);

Modified app initializer that defines the promise in the resolve property:

angular.module("maineMap", ["ngResource", "ngRoute", "configuration"])
.config(["$routeProvider", function($routeProvider){
    $routeProvider.when("/", {
        templateUrl : "views/main.html",
        controller : "MapCtrl",
        resolve : {
            "MapData" : function(MapService){
                return MapService.getMapPaths().promise;
            },
            "CityData" : function(MapService){
                return MapService.getCityPositions().promise;
            }
        }
    });
}]);

Modified controller, where the data fetch is loading prior to the view:

angular.module("maineMap")
.controller('MapCtrl', ['$scope','MapService', function($scope, MapService){
    $scope.mapData = MapService.getMapPaths().getData();
    $scope.cityData = MapService.getCityPositions().getData();
    $scope.currentCounty = {};
    console.log("MapData: " + $scope.mapData);
}]);

The result of this is that prior to the directive's tag being processed within the view template, a data fetch executes and saves the results on the scope, making it available to the directive.

Another option, proposed by Martin Atkins, would have loaded the data into an angular.constant() variable. Since I'm planning on extending this application for several views, the ngRoute combined with resolve promises was the appropriate solution.

Community
  • 1
  • 1
Jason
  • 11,263
  • 21
  • 87
  • 181