1

The situation is that, I have "routeChangeSuccess" event at (run). That event change the value of $rootScope.authenticated based on the REST request response status code.

But when the event is triggered and I try to print to the console the value of $rootScope.authenticated in one of my controllers, it says it is "undefined" value.

Angular run:

pos.run(function($rootScope,$log,apiService){

// register event
$rootScope.$on('$routeChangeSuccess', function () {

    // call api function
    apiService.is_authenticated().then(
        function(response){

            if(response.status == "AUTHENTICATED"){
                $rootScope.authenticated = true;

            }else if(response.status == "NOT_AUTHENTICATED"){
                $rootScope.authenticated = false;
            }
        });       
    })
});

The controller:

// controller
pos.controller('TestController', function($rootScope,$scope,$http) {
    console.log($rootScope.authenticated);
});

It should print "true" or "false". I'm aware it assigns the value after the control executed but how to delay executing the controller until that value assigned ?

Hatem
  • 123
  • 11
  • 1
    Are u sure the `$routeChangeSuccess` event is already triggered when you need it... If no route changes the code inside your Run block is not fired – Jordy van Eijk Feb 02 '16 at 14:16
  • @JordyvanEijk i update the question, have a look ! – Hatem Feb 02 '16 at 14:22
  • 1
    Are you sure the call to apiService.is_authenticated() returned and set you $rootScope property? before you log to the console – Jordy van Eijk Feb 02 '16 at 14:24
  • @JordyvanEijk "It should print "true" or "false". I'm aware it assigns the value after the control executed but how to delay executing the controller until that value assigned ?" – Hatem Feb 02 '16 at 14:25
  • 1
    simple to check put 2 breakpoint in your code on on inside the `then` function in side your run and the other one on the `console.log()` see what hits first – Jordy van Eijk Feb 02 '16 at 14:25
  • You could broadcast inside the success call after setting the property and then subscribe inside the controller... – Jordy van Eijk Feb 02 '16 at 14:27

2 Answers2

2

The fact is that if you use a $promise pattern you can never guarantee the order of execution as it is asynchronous, or having a "secure" enough delay.

You may want to change somewhere else, one way is to make it synch:

if (apiService.is_authenticated()){
   $rootScope.authenticated = true;
}

another is to have $broadcast or $emit and listen to it maybe in the "run" again

$rootScope.$on("LOGIN_SUCCESSFUL", function () {
   $rootScope.authenticated = true;
});

You may combine to $watch

Or use http Interceptor

Or more promises and/or chaining promises

Many ways, but that`s up to you!

Community
  • 1
  • 1
vinjenzo
  • 1,490
  • 13
  • 13
1

Recently worked on the same solution and read lots of answers here on SO. There's actually lots to find but none of the accepted answers actually worked for me. I found one answer that works with the latest ui-router but it isn't accepted as such:

It's calls a preventDefault on the stateChangeStart event, then you can fullfill your promise. if successful then it sets a bypass flag and it transitions again to the same state but skips the promise/resolve part because of the bypass flag:

$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {

    if($rootScope.stateChangeBypass || toState.name === 'login') {
        $rootScope.stateChangeBypass = false;
        return;
    }

    event.preventDefault();

    Auth.getCurrentUser().then(function(user) {
        if (user) {
            $rootScope.stateChangeBypass = true;
            $state.go(toState, toParams);
        } else {
            $state.go('login');
        }
    });

});

Taken from: https://stackoverflow.com/a/28827077/2019281

Another option i'm currently working on is using the resolve object of the root state definition. Resolve is perfect for this since it will stop the controller from initializing untill the promise is resolved or rejected. You can throw an error upon rejection which you can catch in the app's run method:

State definition:

$stateProvider.state('admin', {
    'url': '/admin',
    'controller': 'adminController',
    'templateUrl': 'admin.html'
    'resolve': {
        'auth': [
            '$q', 'Auth',
            function ($q, Auth) {
                return Auth.getCurrentUser().then(
                    function resolved (user) {
                        return (user) ? user : $q.reject('authRejected');
                    },
                    function rejected (user) {
                        return $q.reject('authRejected');
                    }
                });
            }
        ]
    }
});

App's run method:

$rootScope.$on('$stateChangeError', function (e, toState, toParams, fromState, fromParams, error) {
    if (error === 'authRejected') {
            $state.go('login');
    }
});

Drawback is here that you'll need to add a root state to the states you want to protect or add that resolve to every state you want to protect. Also you'll always need to use reload: true on statechanges so the resolve will reload on every statechange.

Inspired by: https://stackoverflow.com/a/24585005/2019281

Community
  • 1
  • 1
iH8
  • 27,722
  • 4
  • 67
  • 76