Search code examples
javascriptangularjsangularjs-directiveangularjs-scopeangularjs-service

Directive with isolate scope property on ngSelect element to pre-select option: It randomly loses value


It looks like this. I load my page and my form displays with values fetched. These values are being fetched by an http service. At the top of the form there is my custom directive (select box):

        .directive('categorySelectBox', function(){

            return {
                restrict: "A",
                replace: true,
                scope: {
                    // all properties here need to be added to 
                    // the directive in order to be picked up
                    taxonomies: '='
                    ,chosen: '='
                },
                templateUrl: "ngapp/js/tpl/select-box.html"
            };

    })

the directive template:

<select class="form-control" 
    ng-options="option.label for option in taxonomies track by option.value" 
    ng-model="chosen" 
    chosen="chosen" 
    taxonomies="taxonomies">
<option value="">Please select a category</option>

My controller is like this:

        .controller('DashboardCtrl', ['$scope', 'DbResourceSrv', function($scope, DbResourceSrv){

            $scope.$watch('cid', function() {
                $scope.formBusy = true;
                $scope.c = DbResourceSrv.getDbResource('response.php', 'company', $scope.cid)
                        .then(function(data) {
                            $scope.c = data;
                            angular.forEach(data, function(value, key) {
                                $scope.c.push({key: value});
                            });
                        });

                $scope.tax = DbResourceSrv.getDbResource('response.php', 'taxonomy', '')
                        .then(function(data) {
                            $scope.taxonomies = [];
                            $scope.chosen = [];
                            angular.forEach(data, function(value, key) {
                                $scope.taxonomies.push({label: value.name, value: value.term_taxonomy_id});
                            });
                            // subtract 1 because $scope.c is 0-based
                            var catId = $scope.c.category - 1;
                            $scope.chosen = $scope.taxonomies[catId];
                            $scope.formBusy = false;
                        });
            });


            $scope.updateCompany = function(cid) {
                var formData = $scope.c;
                $scope.formBusy = true;
                $scope.doCompanyUpdate = DbResourceSrv.updateDbResource('response.php', cid, formData)
                        .then(function(response) {
                            $scope.formBusy = false;
                        });
            };
    }]);

Usually I do see my category (which is also fetched from the database, just in a separate call $scope.tax

Now, I tried with $watching the chosen scope property but it doesnt change anything. I also tried using a directive controller to make sure scope.chosen is set there but it seems to me even though I'm loading both processes with promises - the category fetching misfires when one is loaded before the other because they are tied too tightly.

Any suggestions for code improvement so i can avoid seeing the default Please select a category option selected on page load?


Solution

  • You were correct: you have two promises which aren't guaranteed to resolve in order, and this is a problem. In the case where the second "beats" the first, it won't be able to calculate catId, since $scope.c isn't populated yet.

    $scope.c = DbResourceSrv.getDbResource('response.php', 'company', $scope.cid)
    .then(function(data) {
        $scope.c = data; // not guaranteed to be set when it's needed
    });
    
    $scope.tax = DbResourceSrv.getDbResource('response.php', 'taxonomy', '')
    .then(function(data) {
        var catId = $scope.c.category - 1;
        $scope.chosen = $scope.taxonomies[catId];
    });
    

    A solution is to use $q.all, which accepts an array of promises as an argument and returns another promise which is resolved only when all of the passed in promises resolve.

    It would look something like this:

    $q.all([$scope.c, $scope.tax]).then(function(results){
        var catId = $scope.c.category - 1;
        $scope.chosen = $scope.taxonomies[catId];
    });
    

    Proof of concept demo

    You should be able to leave the bulk of your existing promise callbacks in tact - just move whatever relies on both AJAX requests having returned.