6

I have an angular controller:

.controller('DashCtrl', function($scope, Auth) {
    $scope.login = function() {
        Auth.login().then(function(result) {
            $scope.userInfo = result;
        });
    };
});

Which is using a service I created:

.service('Auth', function($window) {
    var authContext = $window.Microsoft.ADAL.AuthenticationContext(...);

    this.login = function() {
        return authContext.acquireTokenAsync(...)
            .then(function(authResult) {
                return authResult.userInfo;
            });

    };
});

The Auth service is using a Cordova plugin which would be outside of the angular world. I guess I am not clear when you need to use a $scope.$apply to update your $scope and when you don't. My incorrect assumption was since I had wrapped the logic into an angular service then I wouldn't need it in this instance, but nothing gets updated unless I wrap the $scope.userInfo = statement in a $timeout or $scope.$apply.

Why is it necessary in this case?

Dismissile
  • 32,564
  • 38
  • 174
  • 263
  • Possible duplicate of [AngularJS : Using scope.$watch and scope.$apply](http://stackoverflow.com/questions/15112584/angularjs-using-scope-watch-and-scope-apply) – Bryan K Jan 28 '16 at 21:09
  • Does `authContext.acquireTokenAsync(...)` use `$http`? – Daniel Jan 28 '16 at 21:23
  • @Daniel Nope. It is a cordova plugin so it will be a hook to call native java code for an Android application. – Dismissile Jan 28 '16 at 21:27
  • So it makes sense, angular's `$http` is what triggers the `digest cycle` with `$apply` on an Async callback event. posted an answer. – Daniel Jan 28 '16 at 21:31

2 Answers2

5

From angular's wiki:

AngularJS provides wrappers for common native JS async behaviors:

...

jQuery.ajax() => $http

This is just a traditional async function with a $scope.$apply() called at the end, to tell AngularJS that an asynchronous event just occurred.

So i guess since your Auth service does not use angular's $http, $scope.$apply() isn't called by angular after executing the Async Auth function.

Whenever possible, use AngularJS services instead of native. If you're creating an AngularJS service (such as for sockets) it should have a $scope.$apply() anywhere it fires a callback.

EDIT:

In your case, you should trigger the digest cycle once the model is updated by wrapping (as you did):

Auth.login().then(function(result) {
   $scope.$apply(function(){
      $scope.userInfo = result;
   });
});

Or

Auth.login().then(function(result) {
    $scope.userInfo = result;
    $scope.$apply();
});
Daniel
  • 6,194
  • 7
  • 33
  • 59
  • Would the recommendation be to add a $scope.$apply inside of the service then? Instead of having to add $scope.$apply to all of the controllers that are using the service? – Dismissile Jan 28 '16 at 21:40
  • I think since the actual assignment happens in the controller, wrapping the `$scope.userInfo = ` statement as you did (or calling `$scope.$apply()` after it) is the best way. otherwise you might trigger the `digest cycle` before the model is updated. – Daniel Jan 28 '16 at 21:52
2

Angular does not know that $scope.userInfo was modified, so the digest cycle needs to be executed via the use of $scope.$apply to apply the changes to $scope.

Yes, $timeout will also trigger the digest cycle. It is simply the Angular version of setTimeout that will execute $scope.$apply after the wrapped code has been run.

In your case, $scope.$apply() would suffice.

NB: $timeout also has exception handling and returns a promise.

Leromul
  • 316
  • 1
  • 2
  • 6