4

I have this state:

  .state('admin.category',{
    url: '/category',
    templateUrl:'views/admin.category.html',
    resolve:{
      category: ['CategoryLoader', function(CategoryLoader){
        return new CategoryLoader();
      }]
    },
  })

This is my service which I resolve.

.factory('CategoryLoader',['Category', '$state', '$q',
  //console.log($state)
  function(Category, $state, $q){
    return function(){
      var delay = $q.defer();
      Category.get({cat_id:$state.params.id}, //not working
        function(category){
          delay.resolve(category);
        },
       function(){
         //delay.reject('Unable to fetch category ' + $state.params.cat_id)
      });
      return delay.promise;
    }
}]);

Everything works if I change $state.params.id to a number. If I console the $state in my service, I get everything, including params. But I can't seem to be using it. It should be equiliant to use $route.current.params.id, which I've used in other projects. How do I do the same thing with ui-router?

Update: some more info

Parent state:

  .state('admin',{
    abstract: true,
    url: '/admin',
    templateUrl:'views/admin.html'
  })

Category factory:

  factory('Category', function($resource){
    return $resource('/api/category/byId/:id/', {id: '@id'});
  })

I'll put together a fiddle if necassary

Joe
  • 4,274
  • 32
  • 95
  • 175

2 Answers2

6

The issue I see here is that you are trying to access the $stateParams before the state is ready. There are several events that happen when changing states.

First is $stateChangeStart where you are notified of the state you are changing to and coming from as well as all of the params in both states.

After this, the resolve starts to happen. The resolve in your case is calling a function that uses $stateParams

After everything is resolved and good (no rejections or errors), and just prior to the $stateChangeSuccess event, the state params are updated in the $stateParams object.

Basically, you cannot access the params for the state you are changing to via the $stateParams object until after the state change is completed. This is done in case the state change is rejected and cannot be changed. In this case, the previous state remains and the previous state's params will be in $stateParams.

As a quick work-around, you can use the $stateChangeStart event to access the toParams (the params for the state you are changing to) and put them somewhere ($rootScope, or a service) so you can access them when you need them.

Here is a quick example with $rootScope

.run(['$rootScope', function($rootScope){
    $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        $rootScope.toParams = toParams;
    });
}]);

.factory('CategoryLoader',['$rootScope', 'Category', '$q',
  function($rootScope, Category, $q){
    return function(){
      var delay = $q.defer();
      Category.get({cat_id: $rootScope.toParams.id}, // <-- notice its using the object we put in $rootScope, not $state or $stateParams
        function(category){
          delay.resolve(category);
        },
       function(){
         //delay.reject('Unable to fetch category ' + $state.params.cat_id)
      });
      return delay.promise;
    }
}]);

Also, I believe your resource is not setup to correctly use the id you are passing. You were using cat_id above, in order to link it to the :id of the URL, you would have to map it as `{id: '@cat_id'}

factory('Category', function($resource){
    return $resource('/api/category/byId/:id/', {id: '@cat_id'});
});
TheSharpieOne
  • 25,646
  • 9
  • 66
  • 78
2

console.log is by reference and asynchronous

UPDATE

I looked inside the source code, It looks like $stateParams that is injected into resolve functions is a local copy and not the global $stateParams. The global $stateParams is also updated only after the state is activated.

Also the state URL should contain the parameter: url: '/category/:id',

From $stateParams Service docs:

$stateParams Service:

As you saw previously the $stateParams service is an object that will have one key per url parameter. The $stateParams is a perfect way to provide your controllers or other services with the individual parts of the navigated url.

Note: $stateParams service must be specified as a state controller, and it will be scoped so only the relevant parameters defined in that state are available on the service object.

So I guess you must do something like this:

State:

.state('admin.category',{
  url: '/category',
  templateUrl:'views/admin.category.html',
  resolve:{
    category: ['CategoryLoader','$stateParams', function(CategoryLoader, $stateParams){
      return CategoryLoader($stateParams);
    }]
  },
})

Factory:

.factory('CategoryLoader',['Category', '$q',
  function(Category, $q) {
    return function($stateParams){
      var delay = $q.defer();
      Category.get({cat_id:$stateParams.id},
        function(category){
          delay.resolve(category);
        },
       function(){
         //delay.reject('Unable to fetch category ' + $stateParams.cat_id)
      });
      return delay.promise;
    }
}]);
Community
  • 1
  • 1
Ilan Frumer
  • 32,059
  • 8
  • 70
  • 84
  • Tried it. Same result. Is there any fiddle out there that demonstrates this? – Joe Jan 30 '14 at 09:45
  • What if instead of returning CategoryLoader, you return CategoryLoader.init($stateParams). Then have init take the state params and return the promise. I'm not sure it would help, but at least the $stateparams should get passed properly to init and then you can deal with them before resolving the promise that is returned. – JimTheDev Apr 15 '14 at 19:47