Angular JS - set and get query string parameter on $stateProvider state - angularjs

I think this is just basically a question of syntax, but I have this routing set up:
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('addAlbum', {
url: "/albums/create/:artistId",
templateUrl: "public/albums/views/album.tpl.html",
controller: "createAlbumController",
data: {
pageTitle: 'Create Album'
},
resolve: {
albums: function (publicArtistServices) {
return publicArtistServices.getAlbums(1);
}
}
});
}]);
And I need that getAlbums() method to use the artistId passed in on the URL:
return publicArtistServices.getAlbums({need artistId from URL here});
I tried guessing at it by using return publicArtistServices.getAlbums($state.params[0]); but that just threw a "$state is not defined" error.
What's the right syntax here?

$stateParams is what you want to use for this.
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('addAlbum', {
url: "/albums/create/:artistId",
templateUrl: "public/albums/views/album.tpl.html",
controller: "createAlbumController",
data: {
pageTitle: 'Create Album'
},
resolve: {
albums: function ($stateParams, publicArtistServices) {
return publicArtistServices.getAlbums($stateParams.artistId);
}
}
});
}]);

You need to include the $state parameter (to "inject" it into your function):
albums: function ($state, publicArtistServices) {
return publicArtistServices.getAlbums($state.params[0]);
}
If that doesn't work, use $route.current.params.artistId as shown here:
https://stackoverflow.com/a/13433335/584846

Related

Resolve not called the second time

I have some routes defined like this :
$stateProvider
.state('app', {
url: '/',
abstract: true,
views: {
'menuContent': {
templateUrl: 'templates/home.html'
}
}
})
.state('app.restricted', {
url: '/restricted',
views: {
'content': {
templateUrl: 'templates/restricted/restricted-dashboard.html',
controller: 'RestrictedController as vmRestricted'
}
},
resolve: {
isGranted: 'isGranted'
}
})
.state('app.restricted.pending', {
url: '/pending',
views: {
'tabsView': {
templateUrl: 'templates/restricted/restricted-manage-pending.html',
controller: 'RestrictedPendingController as vm'
}
},
resolve: {
isGranted: 'isGranted'
}
})
.state('app.restricted.devices', {
url: '/devices',
views: {
'tabsView': {
templateUrl: 'templates/trusted/restricted-manage-devices.html',
controller: 'RestrictedDevicesController as vm'
}
},
resolve: {
isGranted: 'isGranted'
}
})
.state('app.grant', {
url: '/grant-access',
views: {
'content': {
templateUrl: 'templates/grant-access.html',
controller: 'GrantAccessController as vm'
}
}
})
;
In these routes I have a restricted area and a grant access page to grant access to the restricted area.
When the isGranted resolve provider is rejected I redirect to the app.grant route.
This is the code doing this :
$rootScope.$on(AngularEvents.STATE_CHANGE_ERROR, _onStateChangeError);
function _onStateChangeError(event, toState, toParams, fromState, fromParams, error){
switch (error) {
case 'accessRejected':
$state.go('app.grant');
break;
}
}
Here is the code of my isGranted provider :
(function() {
'use strict';
angular.module('app')
.provider('isGranted', isGrantedProvider);
isGrantedProvider.$inject = [];
function isGrantedProvider() {
this.$get = isGranted;
isGranted.$inject = ['$q', '$log', 'grantService'];
function isGranted($q, $log, grantService){
$log.log('isGrantedProvider');
if (grantService.isGranted()) {
return $q.when(true);
} else {
return $q.reject('accessRejected');
}
}
}
})();
(grantService.isGranted() just returns a boolean value)
The first time I go to the app.restricted route with $state.go('app.restricted') the provider is executed.
The route is rejected because the access is not granted and we are redirected to the app.grant route.
In this page, the user can log in and have access to the restricted area. Once the user is logged in we redirect him to the app.restricted.pending route but the resolve is not called and the route is rejected and we are redirected to the app.grant route again, whereas the access was granted.
Why is the resolve not called?
Is there a way to force it?
EDIT
I have new information after some testing.
I saw that the resolve is not called the second time only when it is a service:
This resolve is always executed when we enter the state:
state('app.restricted', {
url: '/restricted',
views: {
'content': {
templateUrl: 'templates/restricted/restricted-dashboard.html',
controller: 'RestrictedController as vmRestricted'
}
},
resolve: {
isGranted: ['$log', function($log) {
$log.log('RESOLVE');
}]
}
})
But this resolve is only executed once even when I enter again to the state:
state('app.restricted', {
url: '/restricted',
views: {
'content': {
templateUrl: 'templates/restricted/restricted-dashboard.html',
controller: 'RestrictedController as vmRestricted'
}
},
resolve: {
isGranted: 'isGranted'
}
})
angular.module('app')
.provider('isGranted', isGrantedP);
isGrantedP.$inject = [];
function isGrantedP() {
this.$get = isGranted;
isGranted.$inject = ['$q', '$log'];
function isGranted($q, $log){
$log.log('RESOLVE');
}
}
Why isn't this service called each time? Is it because a service is a singleton? How should I proceed?
After a lot of investigations and testing I found the solution!
First, let's see why it is not working
As mentioned in the docs (http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider), if the resolve is a string, then it corresponds to a service
factory - {string|function}: If string then it is alias for service.
Otherwise if function, it is injected and return value it treated as
dependency. If result is a promise, it is resolved before its value is
injected into controller.
And as mentioned in the angularjs docs (https://docs.angularjs.org/guide/providers), all services are singletons, meaning that it will be instantiated only once
Note: All services in Angular are singletons. That means that the
injector uses each recipe at most once to create the object. The
injector then caches the reference for all future needs.
Why is it important?
Because resolves do not call a function inside our service. They just use the return value of the instantiated service. BUT because our service will be instantiated only once, the return value will always be the same! (because our service initialization is only called once)
What can we do?
From my tests I could see that a resolve defined like this:
resolve: {
myResolve: ['$log', function($log) {
$log.log('My Resolve!');
}]
}
is always executed, so we can write them this way to make it work correctly.
But how can I do if I want to use my service?
The best working solution I found to be able to use my service and have a syntax that looks similar to this one: myResolve: 'myResolveService' is to declare my resolve like this:
resolve: {
myResolve: ['myResolveService', function(MyResolveService) {
myResolveService.log();
}]
}
And my service like this:
angular.module('app')
.factory('myResolve', myResolve);
myResolve.$inject = ['$log'];
function myResolve($log) {
function service(){
this.log = log;
function log() {
$log.log('My resolve!');
}
}
return new service();
}
This code can also be adapted for resolves that return a promise:
Resolve:
resolve: {
myResolve: ['myResolveService', function(MyResolveService) {
return myResolveService.check();
}]
}
Service:
angular.module('app')
.factory('myResolve', myResolve);
myResolve.$inject = ['$q', 'myService'];
function myResolve($q, myService) {
function service(){
this.check = check;
function check() {
var defer = $q.defer();
if (myService.check()) {
defer.resolve(true);
} else {
defer.reject('rejected');
}
return defer.promise;
}
}
return new service();
}

Angular ui-router resolve not working

I am trying to load a get service JSON function in the main state resolve function so I can store the data to a scope variable.
The account JSON information is relevant because all sub pages are essentially dependent on the information.
--
The below code is partially working. The account resolve function is being successfully called and even the $http returns a promise (state === 0 though). The issue is when the account function resolves the state.controller is never being called.
$stateProvider
.state('app',{
url: '/',
views: {
'header': {
templateUrl: '../views/templates/partials/header.html',
},
'content': {
templateUrl: '../views/templates/partials/content.html'
},
'footer': {
templateUrl: '../views/templates/partials/footer.html',
}
},
resolve: {
account: function($timeout, accountFactory){
//Comment
return $http({method: 'GET', url: '/account.json'});
}
},
controller: ['$scope', 'account', function($scope, account){
// You can be sure that promiseObj is ready to use!
$scope.data = account;
console.log('SCOPE!!!!!');
}],
})
.state('app.accessory', {
url: 'accessory',
views: {
'content#': {
templateUrl: '../views/accessory/listing.html',
controller: 'accessoryListingCtrl',
controllerAs: 'vm'
}
}
})
}]);
Your parent state config is not correct. When using multiple named views A controller does not belong to a state but to a view, so you should move your controller statement to the specific view declaration, or all of them if you need it everywhere.
See here: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
$stateProvider
.state('report',{
views: {
'filters': {
templateUrl: 'report-filters.html',
controller: function($scope){ ... controller stuff just for filters view ... }
},
'tabledata': {
templateUrl: 'report-table.html',
controller: function($scope){ ... controller stuff just for tabledata view ... }
},
'graph': {
templateUrl: 'report-graph.html',
controller: function($scope){ ... controller stuff just for graph view ... }
},
}
})
I don't know why the controller does not get called. But you can start by making sure that resolve always return data.
resolve: {
account: function($timeout, accountFactory){
//Comment
return $http({method: 'GET', url: '/account.json'})
.$promise.then(
function(data) { return data; },
function(error) { return error; });
}
}

Error: [$injector:unpr] on tring to get resolve

I have a state with resolve:
function config($stateProvider, $urlRouterProvider) {
$stateProvider
.state('emails.list', {
url: "/list/:folder/:page",
parent: 'emails',
templateUrl: "views/emails.list.html",
data: { pageTitle: 'Mail Inbox' },
resolve: {
searchResult: ['Emails', function (Emails) {
return 1;
}]
}
});
}
And I have a controller for this state:
function EmailListCtrl($scope, searchResult, $stateParams, $state) {
...
}
angular.module('peachy').controller('EmailListCtrl', EmailListCtrl);
When I try to pass searchResult to controller I get an error [$injector:unpr]. How it can be fixed? (Or may be something wrong in configuration)
Angular version is 1.3.6
My mistake:
I called EmailListCtrl using ng-controller. Right way is setting controller name in state:
function config($stateProvider, $urlRouterProvider) {
$stateProvider
.state('emails.list', {
...
controller: 'EmailListCtrl'
});
}
There is a working plunker. Try to be sure that even EmailListCtrl recives all the needed dependencies properly:
function EmailListCtrl($scope, result, $stateParams, $state) {
$scope.data = { searchResult : result };
};
EmailListCtrl.$inject = ['$scope','searchResult','$stateParams','$state'];
the $inject contains clear definition for angluar IoC ... so I could even use result as param name instead of searchResult
Check it here

Using an Angular factory for route resolves

Using UI-Router, but I'm not sure that it matters.
What I'm trying to do is extract away my current resolve logic into a factory/service, such that it doesn't pollute my $stateProvider definition. This is a short example:
Here's my current config: ($kinvey is a service that returns a promise)
.state('home', {
url: '/',
views: {
'header': headerView,
'content': {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
},
'footer': footerView
},
resolve: {
user: ['$kinvey', function ($kinvey) {
return $kinvey.User.me();
}]
}
})
And what I'd like it to be:
resolve: {
'user': 'DataResolverService.userResolve'
}
DataResolverService
.factory('DataResolverService', function DataResolverService($kinvey) {
var userResolve = {
user: ['$kinvey', function ($kinvey) {
return $kinvey.User.me();
}]
};
// public API
return {
userResolve: userResolve
};
});
I seem to be getting a Unknown provider: DataResolverService.userResolveProvider <- DataResolverService.userResolve. Is this because factories/services can't be run like this inside the config() block?
This is because you are using a service in the config block. You should be able to inject it like this:
resolve: {
'user': ['DataResolverService', function(DataResolverService){
return DataResolverService.userResolve;
}]
}
An alternative approach would be to hide the injection mess in a constant (don't forget to include $injector as a dependency):
resolve: $injector.get('ResolveMap')
app.constant('ResolveMap',{
'user': ['DataResolverService', function(DataResolverService){
return DataResolverService.userResolve;
}]
});
There was a discussion around this here: https://groups.google.com/forum/#!topic/angular/QtO8QoxSjYw

ui-router resolve with dynamic parameters

This is probably simple but I can't find anything in the docs and googling didn't help. I'm trying to define a state in $stateProvider where the URL I need to hit on the server to pull the needed data depends on a state URL parameter. In short, something like:
.state('recipes.category', {
url: '/:cat',
templateUrl: '/partials/recipes.category.html',
controller: 'RecipesCategoryCtrl',
resolve: {
category: function($http) {
return $http.get('/recipes/' + cat)
.then(function(data) { return data.data; });
}
}
})
The above doesn't work. I tried injecting $routeParams to get the needed cat parameter, with no luck. What's the right way of doing this?
You were close with $routeParams. If you use ui-router use $stateParams instead.
This code works for me:
.state('recipes.category', {
url: '/:cat',
templateUrl: '/partials/recipes.category.html',
controller: 'RecipesCategoryCtrl',
resolve: {
category: ['$http','$stateParams', function($http, $stateParams) {
return $http.get('/recipes/' + $stateParams.cat)
.then(function(data) { return data.data; });
}]
}
})
For those who are using ui-router 1.0 $stateParams is deprecated, you should use $transition$ object instead:
.state('recipes.category', {
url: '/:cat',
templateUrl: '/partials/recipes.category.html',
controller: 'RecipesCategoryCtrl',
resolve: {
category: ['$http','$transition$', function($http, $transition$) {
return $http.get('/recipes/' + $transition$.params().cat)
.then(function(data) { return data.data; });
}]
}
})

Resources