2

I am building routes/states and the menu based on what the user is authorized to see. I've looked around and tried a few different things, but i'm hitting a brick wall. The SessionService object in the RoleService Factory is empty whenever RoleService.validateRole() is called. No route is added and the app is effectively dead. Why is the injected factory empty and the methods undefined.

Here is a simplified layout of the app starting in order of dependencies.

In app.run(), I am adding the states to the app instead of doing it in the config.

$stateProviderRef.state(value.stateName, state);

The states come from (a factory) AppConfig.getStates(), which returns an array.

var states = AppConfig.getStates();

In getStates() we validate each route's role.

if(RoleService.validateRole(routes[i].role))

The RoleService depends on the SessionService and the validateRole function does this check:

if(SessionService.currentUser.role === role)

The SessionService depends on the AuthenticationService which is just a factory that returns a promise using $http (the user object). The SessionService.currentUser is a function that .then()s the returned promise from the AuthenticationService.

 return {
     currentUser: function(){
         AuthenticationService.then(function(result){
             return result;
         });
     }
 };

I'm not sure of a better way to explain the code without including the entire files.

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
schumacherj
  • 1,284
  • 5
  • 18
  • 33

1 Answers1

1

Based on the plunker (mentioned in comment), I updated/cloned it to another, which is working

I. simple - when static data are returned (no $http)

Because the service SessonService was defined like this:

return {
    currentUser: function() {
    ...

we cannot call it as a property:

...
return {
    validateRoleAdmin: function () {
        if (SessionService.currentUser.role === 'admin') {
        ...
    },

    validateRole: function (role) {
        if(SessionService.currentUser.role === role){
        ...

it is a function it must be called as a function currentUser():

return {
    validateRoleAdmin: function () {
        if (SessionService.currentUser().role === 'admin') {
        ...
    },

    validateRole: function (role) {
        if(SessionService.currentUser().role === role){
        ...

II. waiting for async calls

The adjusted example

Next, if we in example create a static result of the service AuthenticationService:

angular.module('daedalus').factory('AuthenticationService', 
    function() {
      return {"idsid": "ad_jdschuma","role": "user","id": "33333"}
    }
)

we cannot expect there will be some then method:

currentUser: function() {
  //AuthenticationService.then(function(result) {
  //  return result;
  //});
  return AuthenticationService;
}

And to make it really async we can replace it with this:

angular.module('daedalus').factory('AuthenticationService', 
    ['$timeout', function($timeout) {

    return {
      getData: function() {
        return $timeout(function() {
          return {
            "idsid": "ad_jdschuma",
            "role": "user",
            "id": "33333"
          }
        })
      }
    };     
 }])

And then use even the .then() - Session service:

angular.module('daedalus').factory('SessionService', ['AuthenticationService', 
    function(AuthenticationService) {

        return {
              currentUser: function(){
                    return AuthenticationService
                      .getData()
                        .then(function(result){
                                return result;
                            });
                        }
        };
  }]
)

And the RoleService:

  return {
    ...
    validateRole: function(route) {
      console.log('SessionService currentUser: ' + JSON.stringify(SessionService))
      return SessionService
        .currentUser()
        .then(function(userRole) {
          if (userRole.role === route.role) {
            return route;
          } else {
            return null;
          }
        })
    }

And with this in place in appConfig

getStates: function(){
    var items = [];
    var deffered = $q.defer();
    var validatedCount = routes.length;
    for(var i=0,len=routes.length; i<len; i++){
      var route = routes[i];
      RoleService
        .validateRole(route) 
        .then(function(route){
            if(route) {
                 items.push(route.stateConfig)
          }
          if(--validatedCount === 0 ){ // all processed
           deffered.resolve(items)
          }
        })
    }
    return deffered.promise;
}

We can do that in run:

AppConfig
  .getStates()
  .then(function(states) {console.log(states)
    angular.forEach(states, function(value, key) {
      var state = {
        "url": value.url,
        "templateUrl": value.templateUrl,
        "controller": value.controller
      };
      $stateProviderRef.state(value.stateName, state);


    });
    // Configures $urlRouter's listener *after* your custom listener            
    $urlRouter.sync();
  });

$urlRouter.listen();

Check it here

The concept of the second solution (async) is too .thenified(). I just intended to show that all is working. Better approach how to get security data is completely covered here:

Confusing $locationChangeSuccess and $stateChangeStart

Community
  • 1
  • 1
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335