Search code examples
angularjscallbackng-class

ngClass not updating after condition is changed in a callback


I am trying to add a 'loading' class to an element until an asynchronous call is finished. To do so, I am using a variable named loading that I use like so:

<div class='ui segment' ng-class="{'loading': loading == true}">

To trigger the call, I am using another element with ng-click :

<i class='...' ng-click="action('deleteGlobal', false)"></i>

This will call the method deleteGlobal of the variable project in my controller's $scope, and provide a callback :

$scope.action = (action, redirect) => {
    delete $scope.error;
    $scope.loading = true;
    $timeout(() => {
        $scope.project[action]((err) => {
            $scope.loading = false;
            if (err)
                $scope.error = err;
            else if (redirect)
                $state.go('projects.all');
        });
    })
}

project is an instance of my custom class ProjectManager. This is its method deleteGlobal :

deleteGlobal (callback) {
    window.setTimeout(() => {
        callback();
    }, 1000)
}

(Here, the setTimeout is only used to simulate a long operation since that is what I plan to replace this code with later on)

So with this code, I expect $scope.loading to become false once the callback is called. And with a couple of console.logs I saw that was indeed the case. However, the 'loading' class of my element is not removed accordingly.

I read about $scope.$apply() and tried to wrap my async call in it but it didn't work. In fact, it even triggered an error: '$apply already in progress'.

Thanks in advance!


Solution

  • Integrating callback-based APIs with AngularJS

    In general with AngularJS, third-party callback-based APIs are integrated with the framework by converting them to $q promises:

    function apiPromise(callbackBasedApi) {
        var defer = $q.defer()
        callbackBasedApi( (value,err) => {
             ̶c̶a̶l̶l̶b̶a̶c̶k̶(̶)̶;̶  
             if (value) {
                defer.resolve(value);
             } else {
                defer.reject(err)
             }; 
        })
        return defer.promise;
    }
    

    Then use the .then method of the returned promise:

    $scope.action = (action, redirect) => {
        delete $scope.error;
        $scope.loading = true;
        var promise = apiPromise($scope.project[action])
          .then(function(value) {
            if (redirect)
               $state.go('projects.all');
            };
        })
          .catch(function(err) {
            $scope.error = err;
        })
          .finally(function() {
            $scope.loading = false;
        });
    }
    

    Promises created with the $q service are integrated with the AngularJS framework and its digest cycle. Operations which are applied in the AngularJS execution context will automatically benefit from AngularJS data-binding, exception handling, property watching, etc.