I'm getting killed by my inability to grok Angular-UI UI-Router. I have a state defined as follows:
$stateProvider
.state('member', {
url: '/member/:membersId',
templateUrl: 'templates/member.html',
resolve : {
// From examples for testing
simpleObj: function(){
return {value: 'simple!'};
},
memberDetails : function(FamilyService,$state) {
return FamilyService.getMember($state.current.params.membersId);
}
},
controller: 'MemberController'
});
Since the docs say $stateParams aren't available, I'm using $state.current.params. Should be fine. Instead, I get dead air. I can't access the membersId to save my life.
To test my service and make sure it's not the problem, I hard coded in a membersId and the controller gets the memberDetails result as well as the simpleObj result. So, Service and Controller and working great.
Example of that hardcoded:
$stateProvider
.state('member', {
url: '/member/:membersId',
templateUrl: 'templates/member.html',
resolve : {
// From examples for testing
simpleObj: function(){
return {value: 'simple!'};
},
memberDetails : function(FamilyService,$state) {
return FamilyService.getMember('52d1ebb1de46c3f103000002');
}
},
controller: 'MemberController'
});
Even though the docs say you can't use $stateParams in a resolve, I've tried it anyway. It doesn't work either.
return FamilyService.getMember($stateParams.membersId);
How in the world do I get the membersId param to get passed into the FamilyService for the resolve?
I don't have much hair left to pull out; so, someone save me please.
I finally figured this out. It was quite simple and in the docs. Despite reading several times, I overlooked it each time. I needed to inject $stateParams into the resolve:
$stateProvider
.state('member', {
url: '/member/:membersId',
templateUrl: 'templates/member.html',
resolve : {
simpleObj: function(){
return {value: 'simple!'};
},
memberDetails : function(FamilyService,$stateParams) {
return FamilyService.getMember($stateParams.membersId);
}
},
controller: 'MemberController'
});
I still don't understand why the documentation says this is not possible.
Two Important $stateParams Gotchas
The $stateParams object is only populated after the state is activated
and all dependencies are resolved. This means you won't have access to
it in state resolve functions. Use $state.current.params instead.
$stateProvider.state('contacts.detail', { resolve: {
someResolve: function($state){
//* We cannot use $stateParams here, the service is not ready //
// Use $state.current.params instead *//
return $state.current.params.contactId + "!"
}; }, // ... })
Related
I am working on an application that photographers can use to upload photos. The frontend is AngularJS and there is a RESTfull api backend.
Because of some issues and the fact that ui-router seems better then ngRouter, I decided to change the $routeprovider to $stateProvider of ui-router.
However, my resolve doesn't work anymore (I guessed it would break but I cannot find the solution to my situation).
So here is the original $routeprovider code:
.when('/photographer', {
templateUrl : '/static/partials/photographer/photographer_dash.html',
controller : 'photographerController',
resolve: {
photogPrepService: function (PhotogService) {
return PhotogService.ownPhotos();
}
}
})
The PhotogService is a $resource service that has the following $resource objects:
return $resource(apiHost, {}, {
'ownPhotos': {
url: apiHost + '/photographer_own_photos/',
method: 'GET',
interceptor: ResponseInterceptor
}
});
In the controller I would then do the following (photogPrepService being injected because of the resolve):
var promise = photogPrepService;
promise.then(
function (data) {
$scope.ownPhotos = data.data.photos;
});
This all worked well and I would get the photos in the scope.
However as said with ui-router it doesn't work and I cannot seem to get it working...
According to the docs (https://github.com/angular-ui/ui-router/wiki#resolve) the following should work:
$stateProvider.state('photographer',
{
url: '/photographer',
templateUrl: '/static/partials/photographer/photographer_dash.html',
controller: 'photographerController',
resolve: {
photogPrepService: function (PhotogService) {
return PhotogService.ownPhotos();
}
}
However, when the resolve is injected in the controller and I use console.log() to print the response, I get the following:
Resource(value)
I somehow cannot seem to get the values (JSON response {"photos": ...}) injected into the controller.. I tried various solutions that have been suggested here on stackoverflow and read guides and the API of ui-router, but I cannot wrap my head around what is going wrong... I hope someone can guide me in the right direction..
Thanks!
I think you can use the code below:
//the PhotogService will keep the same as before
//inject the 'PhotogService' into the controller photographerController
PhotogService.ownPhotos().then(function(data) {
$scope.ownPhotos = data.data.photos;
});
//instead of injecting the photogPrepService through 'resove', inject the PhotogService into the controller
//for the $stateProvider
$stateProvider.state('photographer',
{
url: '/photographer',
templateUrl: '/static/partials/photographer/photographer_dash.html',
controller: 'photographerController',
}
You need to give a promise to the resolve, your ressource should look more like :
$stateProvider.state('photographer',
{
url: '/photographer',
templateUrl: '/static/partials/photographer/photographer_dash.html',
controller: 'photographerController',
resolve: {
photogPrepService: function (PhotogService) {
return PhotogService.ownPhotos().$promise;
}
}
});
Or modify your ressources to make them promises.
Here is an excerpt from my $stateProvider configuration
.state('main', {
url: '/',
auth: true,
views: {
'#': {
templateUrl: 'main.html',
controller: 'MainController'
},
'content#main' : {}
},
resolve: {
$user: function ($q, Session) {
return Session.user();
}
},
onEnter: function($state, Session, $timeout) {
$timeout(function() {
var role = Session.checkUserRole();
if (role !== 'administrator') {
$state.go('redirect');
}
}, 0);
}
})
What happens is that even if the role is not 'administrator', it still executes all the codes that are in MainController. Previously, I have the logic in my $stateChangeStart but Session.checkUserRole() is always returning null after I just finished signing in. This is most likely due to the nature of Javascript being asynchronous. And yes, it is true that the commands in my run.js finishes first before the promise in the Auth function I called. :(
I was searching if resolve can be written differently to accomodate what I want to happen.
I also didn't like the idea of putting $state.go inside MainController because you will still be able to see the application though only for like 500ms. Still very bad! And even if I get redirected to the warning page, I still see my app made calls to the backend and was able to retrieve data.
If Session.checkUserRole() was returning the correct value and not null inside my .run, I would have been done
Any thoughts/ideas?
I got it fixed finally after converting the function in Session factory into a promise then using that promise in stateProvider. :)
I'm trying to get some config settings from an API before loading my view so I don't get a billion interpolation errors from trying to use undefined data. The app doesn't crash because of the errors but I need to get rid of the errors nontheless.
I figured I'd use a resolve and wait for the config method to resolve before loading the view but I'm unable to get the data to my controller so that I can use it there. I've been looking at other questions and apparently this is the way you should do it.. What am I doing wrong exactly?
The issue is not in the api factory, the api.getConfig() returns a promise of the $http.get request to the external API just fine so I won't include the function snippet here.
In my routing:
.when('/:category/top_rated', {
templateUrl: 'views/pages/results.html',
resolve: {
'isAuth': ['fbRefs', function(fbRefs) {
return fbRefs.getAuthObj().$requireAuth();
}],
'getConfig': ['api', function(api) {
api.getConfig().then(function(data) {
return data;
});
}]
}
})
Then in my controller I inject it like this:
core.controller('MainCtrl', ['getConfig', function(getConfig) {
getConfig.then(function(data) {
console.log(data);
});
}]);
However this throws a $injector:unpr error. Why can't it find the resolve method?
Your controller should be declared in the router, otherwise the controller has no context where 'getConfig' is coming from, thus an injector error.
.when('/:category/top_rated', {
templateUrl: 'views/pages/results.html',
controller: 'MainCtrl',
resolve: {
'isAuth': ['fbRefs', function(fbRefs) {
return fbRefs.getAuthObj().$requireAuth();
}],
'getConfig': ['api', function(api) {
api.getConfig().then(function(data) {
return data;
});
}]
}
})
I am trying to get the query parameters from my angular UI router application. However I cannot seem to get them when I use the query method. The state params are always coming through as undefined.
$stateProvider.state("message",
{
views: {
"main-view": {
templateUrl: "admin/templates/message.html",
controller: ["$scope", "$stateParams", function ($scope, $stateParams )
{
// Get the params
$scope.message = $stateParams.message; // undefined
$scope.error = $stateParams.status; // undefined
}]
}
},
url: "/admin/message?message&status"
})
}
The URL i am using to access this state is:
http://localhost:8080/admin/message?message=hello&status=error
However if I use the Basic Parameters method it works fine. I.e:
url: "/admin/message/:message/:status"
...
http://localhost:8080/admin/message/hello/error
I have html5 mode activated (not sure if that affects it?)
My code has been simplified for the above - am I doing something wrong here? Have I forgot to include anything?
Thanks
What's the correct way to update a ui-router view when state parameters change?
For example, if I've got a state like:
.state("page.view", {
url: "/pages/:slug",
views: {
"": {
controller: "PageCtrl",
templateUrl: "page-view.html",
},
},
})
And an (incorrect) controller which looks like this:
.controller("PageCtrl", function($scope, $state) {
$scope.page = loadPageFromSlug($state.params.slug);
})
How can I correctly load a new $scope.page when the $state.slug changes?
Note that the above does not work when moving from page to another because the controller is only run once, when the first page loads.
I would do something like this:
.controller("PageCtrl", function($scope, $state) {
$scope.$on("$stateChangeSuccess", function updatePage() {
$scope.page = $state.params.slug;
});
});
I'd be curious if you find a better way - there may be some way to just watch the value of the state slug, but this is clean and clearly articulates what it is that you're watching for.
I am really not fully sure, if I do not miss something here - but, based on the snippets shown in your question:
PageCtrl is related to state "page.view" and will be run as many times as "page.view" state is triggered
"page.view" state has declared param slug - url: "/pages/:slug",, which will trigger state change - whenever it is changed
If the above is true (if I do not oversee something) we can use stateConfig setting - resolve
there is no need to use $state.params. We can use $stateParams (more UI-Router way I'd personally say)
Well if all that is correct, as shown in this working plunker, we can do it like this
resolver:
var slugResolver = ['$stateParams', '$http'
, function resolveSlug($stateParams, $http){
return $http
.get("slugs.json")
.then(function(response){
var index = $stateParams.slug;
return response.data[index];
});
}];
Adjusted state def:
.state("page.view", {
url: "/pages/:slug",
views: {
"": {
controller: "PageCtrl",
templateUrl: "page-view.html",
resolve: { slug : slugResolver },
},
},
})
And the PageCtrl:
.controller('PageCtrl', function($scope,slug) {
$scope.slug = slug;
})
Check it all in action here
I had this problem in ui-router 0.2.14. After upgrading to 0.2.18 a parameter change does fire the expected $stateChange* events.