Search code examples
angularjsangularjs-scoperestangular

reacting on my parent controller to be done with loading


I have a MainController that loads an array of Objects via RestAngular

controllers.controller('MainController', $scope, Restangular) {
    $scope.colors = {};
    Restangular.all('colors').getList().then(function(colors) {
        angular.forEach(colors, function(c) {
            c.brightness = getBrightness(c);
            $scope.colors[c.id] = c;
        });
    });
};

And I have a routing to have a sub-page for dealing with colors.

$stateProvider
   .state('picture', {
           abstract: true,
           url: "/picture/{pictureId:[0-9]{1,6}}",
           templateUrl: "partials/picture.html",
           controller: 'MainController'
          }
   )
   .state('picture.colors', {
           url: "/colors",
           templateUrl: "partials/picture_colors.html",
           controller: 'PictureColorsController'
           }
   );

Here, I want to have a drop-down menu to multi-select colors.

<multiselect ng-model="selector.colors"
             options="c as c.name for c in colors| orderBy:'brightness'"
             data-multiple="true"
             header="Colors">
</multiselect>

This works well. But when the page is loaded, I want all colors to be selected. So what I want is this:

controllers.controller('PictureColorsController', $scope) {
    $scope.selector = {colors:[]};
    var selectAll = function() {
        $scope.selector.colors.splice(0, $scope.selector.colors.length);
        angular.forEach($scope.colors, function(c) {
            $scope.selector.colors.push(c);
        });
    };
    selectAll();
};

But at the time the the child controller 'PictureColorController' is executed, the colors aren't loaded yet. So the only solution I can think of is following, but I don't really like it. It feels like I'm doing something wrong.

controllers.controller('MainController', $scope, Restangular) {
    $scope.colors = {};
    var colorsCallback = undefined;
    $scope.registerColorsCallbackFn = function(func) {
        colorsCallback = func;
    };
    Restangular.all('colors').getList().then(function(colors) {
        angular.forEach(colors, function(c) {
            c.brightness = getBrightness(c);
            $scope.colors[c.id] = c;
        });
        if (colorsCallback) {
            colorsCallback();
        }
    });
};

and

controllers.controller('PictureColorsController', $scope) {
    $scope.selector = {colors:[]};
    var selectAll = function() {
        $scope.selector.colors.splice(0, $scope.selector.colors.length);
        angular.forEach($scope.colors, function(c) {
            $scope.selector.colors.push(c);
        });
    };
    selectAll();
    $scope.registerColorsCallbackFn(selectAll);
};

Is there a cleaner way to do this? There are several lists of data that I want to load in my MainController that all need to be loaded before I want to execute any of the child controllers. Is there a good mechanism for that?


Solution

  • You can use ui-router's resolve property on the state:

    .state('picture.colors', {
               url: "/colors",
               templateUrl: "partials/picture_colors.html",
               controller: 'PictureColorsController'
               },
               resolve: {
                 colors: function(Restangular) {
                   return Restangular.all('colors').getList();
                 }
               }
       )
    

    ui-router will resolve every promise in the resolve object before executing your controller. And it'll give you a colors parameter you can inject in your controller with the data that the promise resolves with.

    There's not necessarily anything wrong with your approach, though. A slightly more structured version is one of the approaches in John Papa's Angular style guide.