stateParams vs $stateParams with ui-router? - angularjs

I am confused. For a long time now I have been using stateParams as a means of find out the stateParams inside a templateUrl.
Now I tried to do the same in a resolve and it does not work. In fact nothing happens when I use stateParams.
However by chance I found that I can use $stateParams in the resolve and it works.
Can someone tell me what is the difference and why do I need to use stateParams in the templateUrl and $stateParams in the resolve?
var auth = {
name: 'auth',
url: '/Auth/:content',
templateUrl: function (stateParams) {
var page = 'app/auth/partials/' + stateParams.content + '.html';
return page;
},
controller: function ($scope, authService) {
$scope.aus = authService;
},
resolve:
{
init: function ($stateParams) {
var x = 99;
return true;
}
}
};

I've created working example here, showing that $statePrams are accessible in the resolve
// States
$stateProvider
.state('auth', {
url: "/auth/:content",
templateUrl: 'tpl.html',
controller: 'AuthCtrl',
resolve : {
init : ['$stateParams' , function($stateParams){
return { resolved: true, content: $stateParams.content };
}]
}
})
Controller
.controller('AuthCtrl', ['$scope', 'init', function ($scope, init) {
$scope.init = init;
}])
and this could be the calls
auth/8
auth/xyz
Check it here

Related

UI Resolve is not injecting into controller (data is undefined)

I've looked at similar questions but I can't seem to understand what I am missing. Basically, I have a service that gets data from the server, and I am trying to get that data into a controller through UI-Router's resolve property. However, after following numerous tutorials and documentations, I can't get the controller to find the data, so to speak. Everything comes up as undefined. I am hoping someone can help me understand what is happening. My code is below.
services.js
myServices.factory('SoundCloudService', ['$http', '$log', '$sce', function($http, $log, $sce) {
function getPlayerHtml() {
return $http.get('/get-site-data').then(function(oEmbed) {
return $sce.trustAsHtml(oEmbed.data.player);
});
};
function getSiteAbout() {
return $http.get('/get-site-data').then(function(oEmbed) {
return $sce.trustAsHtml(oEmbed.data.about);
});
}
function getAllTracks() {
return $http.get('/get-all-tracks').then(function(tracks) {
return JSON.parse(tracks.data);
});
};
function getAllPlaylists() {
return $http.get('/get-playlists').then(function(playlists) {
return JSON.parse(playlists.data);
})
};
function getPlaylist(pid) {
return $http.post('/get-playlist', pid, $http.defaults.headers.post).then(function(playlist) {
return playlist.data;
});
};
function getXMostTrendingFrom(x, playlist) {
var i, trending = [];
playlist.sort(function(a, b) { return b.playback_count - a.playback_count} );
for(i=0;i<x;i++) {
trending.push(all_tracks[i]);
}
return trending;
};
return {
getAllTracks: getAllTracks,
getAllPlaylists: getAllPlaylists,
getPlayerHtml: getPlayerHtml,
getSiteAbout: getSiteAbout,
getXMostTrendingFrom: getXMostTrendingFrom,
getPlaylist: getPlaylist,
};
}]);
app.js
myApp.config(['$stateProvider', '$urlRouterProvider', 'ngMetaProvider',
function($stateProvider, $urlRouterProvider, ngMetaProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('main', {
url: '',
template: '<ui-view/>',
abstract:true,
controller: 'MainController',
resolve: {
player: function(SoundCloudService) { return SoundCloudService.getPlayerHtml(); },
about: function(SoundCloudService) { return SoundCloudService.getSiteAbout(); },
}
})
.state('main.home', {
url: '/',
templateUrl: '../static/partials/home.html',
controller: 'IndexController',
})
.state('main.team', {
url: '/team',
templateUrl: '../static/partials/team.html',
controller: 'TeamController',
})
.state('main.contact', {
url: '/contact',
templateUrl: '../static/partials/contact.html',
controller: 'ContactController',
})
.state('main.resources', {
url: '/resources',
templateUrl: '../static/partials/resources.html',
controller: 'ResourcesController',
})
.state('main.listen-to', {
url: '/listen-to',
templateUrl: '../static/partials/listen-to.html',
controller: 'ListenController',
})
.state('main.listen-to.season', {
url: '/listen-to/:season',
templateUrl: '../static/partials/listen-to.season.html',
controller: 'ListenController',
})
.state('main.listen-to.season.episode', {
url: '/listen-to/:season/:episode',
templateUrl: '../static/partials/listen-to.season.episode.html',
controller: 'ListenController',
})
.state('main.read', {
url: '/read',
templateUrl: '../static/partials/read.html',
controller: 'ReadController',
})
.state('main.read.post', {
url: '/read/:post',
templateUrl: '../static/partials/read.post.html',
controller: 'ReadController',
})
}
]);
controller.js
myControllers.controller('MainController', ['$scope', '$log', 'PageTitleService',
function($scope, $log, PageTitleService, player) {
$log.log(player); /* This is always undefined */
}
]);
[UPDATE]
As pointed out by Hadi in the answer below, I placed player in the array, and the controller now looks like this:
skodenControllers.controller('MainController', ['$scope', '$log', '$sce', 'PageTitleService', 'player',
function($scope, $log, $sce, PageTitleService, player) {
$log.log(player);
}
]);
The console DOES show the data, but only after an error as such:
Error: [$injector:unpr]
http://errors.angularjs.org/1.3.2/$injector/unpr?p0=playerProvider%20%3C-%20player
at angular.js:38
at angular.js:3930
at Object.d [as get] (angular.js:4077)
at angular.js:3935
at d (angular.js:4077)
at Object.e [as invoke] (angular.js:4109)
at F.instance (angular.js:8356)
at angular.js:7608
at r (angular.js:347)
at I (angular.js:7607)
Hopefully someone can lead me in the right direction.
You forgot pass player into array. change to this
myControllers.controller('MainController', ['$scope', '$log',
'PageTitleService','player',
function($scope, $log, PageTitleService, player) {
$log.log(player); /* This is always undefined */
}
]);
As myServices and myControllers are both modules, ensure you add them as dependencies of myApp module.
// init myApp module
angular.module('myApp', ['myServices', 'myControllers']);
Edit
Some leads :
According to the documentation, when using ui-router nested views, child views (state name = main.xxx) must declare the parent state, so you must add parent: "main" or child views won't inherit resolved properties of main state controller
As siteDate is loaded asynchronously in SoundCloudService (services.js:23), you cannot be sure it will be available in your controllers which are loaded at the same time.
Instead, add a getSiteDate() method to SoundCloudService which returns a promise. siteData is then cached and immediately return by the promise.
For example :
/**
* #name getSiteData
* #description Scrap site data
* #returns {promise} a promise
*/
function getSiteData() {
var deferred = $q.defer();
if(siteData) {
deferred.resolve(siteData);
}
else {
$http.get('/get-site-data').then(function(response) {
siteData = response.data;
deferred.resolve(siteData);
}, function(err) {
deferred.reject(err.message);
});
}
return deferred.promise;
}
Why trying to map SoundCloudService to siteData ? You should simply inject SoundCloudService in controllers that use it :
For example :
skodenControllers.controller('MainController', ['$scope', '$log', '$sce', 'PageTitleService', 'SoundCloudService',
function($scope, $log, $sce, PageTitleService, SoundCloudService) {
// Note: getSiteData() could use a cache inside the service
SoundCloudService.getSiteData().then(function(siteData) {
...
});
}

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();
}

stateParams not available when I call state.go()

So I am having trouble with stateParams - I have done this before and have never had an issue but I can't seem to pass anything to the state when I call go.
If you look at the demographics section you can see that I am returning a function for the template URL and it works just fine when I hard code the GUID however my personId variable is always undefined. I am trying to figure out what I have done to cause this however I can't for the life of me.
memberApp.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
$stateProvider.state('quickScreen', {
url: "#/Components/QuickScreen",
templateUrl: "/Components/QuickScreen/index.html",
controller: "quickScreenController",
});
$stateProvider.state('quickScreen.detail', {
url: '#/quickScreen/:step',
templateUrl: function ($stateParams) {
console.log($stateParams.step);
return "/Components/QuickScreen/" + $stateParams.step + "/" + $stateParams.step + ".html";
}
});
$stateProvider.state('demographics', {
views: {
"memberDemographics": {
templateUrl: function ($stateParams) {
console.log("PersonId: " + $stateParams.personId);
//return '/Demographics/Index/' + $stateParams.personId;
return '/Demographics/Index/' + 'f75fb494-xxxx-xxxx-xxxx-a17b01336ca5';
},
controller: 'demographicsSearchController'
}
},
});
});
memberApp.controller('memberAppController', function ($scope, $state, $stateParams) {
console.log("About to load to demographics");
$state.go('demographics', { personId: 'f75fb494-fe7a-4564-be93-a17b01336ca5' });
});
You can try with this ,We can take Particular Id from stateParams and assign to some variable you can use that variable or u can user direactly
( $state.go('demographics', { personId: $stateParams.personId });).
memberApp.controller('memberAppController','$stateParams','$scope','$scope',
function ($scope,$state, $stateParams)
{
console.log("About to load to demographics");
var Id = $stateParams.personId;
$state.go('demographics', { personId: Id });
});

ui-router: $stateParams is empty in resolves

Besides ui-router, I am using ui-bootstrap's $modal service.
I use resolves (actually passed inside a modal) on the onEnter property of the state (with url parameters) to activate modals (as mentioned in the docs|FAQ of ui-router).
I tried to access the $stateParams, however it seems to be an empty object when the resolves fire.
function onEnter($modal, $state) {
// simple handler
function transitionToOverlay() {
return $state.transitionTo('parent');
}
// actual modal service
$modal
.open({
size: 'sm',
resolve: { getY: getY },
controller: 'ChildCtrl as child',
template: template
})
.result
.then(transitionToOverlay)
.catch(transitionToOverlay);
}
// resolve
function getY($state, $stateParams) {
console.log('State resolve getY...');
console.log($stateParams); // returns {} empty object
return 'y'; // just a dummy resolve
}
Here's a plnkr for demonstration purposes.
UI-Router doesn't have any control over your $modal call. Resolves should go on state definitions if you would like UI-Router to inject them.
var state = {
url: '{id}',
name: 'parent.child',
resolve: { getY: getYfn }, // moved from $modal call
onEnter: function(getY) { // injected into onEnter
$modal.open({
resolve: { getY: function () { return getY; } }, // passed through to $modal.open
controller: 'ChildCtrl as child', // ChildCtrl injects getY
});
}
}
Just posting this in case someone has the same problem...
I had the same problem as in the original question but the selected answer didn't help me too much since I couldn't get to access the resolve defined directly in the state inside my modal controller.
However, I noticed $stateParams is still accessible in the onEnter function so it is possible to create a variable here and then use this variable inside the $modal.open() function.
.state('parent.child', {
url: 'edit/:id',
// If defining the resolve directly in the state
/*resolve: { // Here $stateParams.id is defined but I can't access it in the modal controller
user: function($stateParams) {
console.log('In state(): ' + $stateParams.id);
return 'user ' + $stateParams.id;
}
},*/
onEnter: function($modal, $stateParams, $state) {
var id = $stateParams.id; // Here $stateParams.id is defined so we can create a variable
$modal.open({
templateUrl: 'modal.html',
// Defining the resolve in the $modal.open()
resolve: {
user: function($stateParams) {
console.log('In $modal.open(): ' + $stateParams.id); // Here $stateParams.id is undefined
console.log(id); // But id is now defined
return 'user ' + id;
}
},
controller: ChildCtrl,
controllerAs: 'ctrl'
})
.result
.then(function(result) {
return $state.go('^');
}, function(reason) {
return $state.go('^');
});
}
})
Here is an example plnkr : http://plnkr.co/edit/wMMXDSsXLABFr0P5q2On
Also, if needing to define the resolve function outside the configuration object, we can do it like this:
var id = $stateParams.id;
$modal.open({
resolve: {
user: myResolveFunction(id)
},
...
});
And:
function myResolveFunction(id) {
return ['MyService', function(MyService) {
console.log('id: ' + id);
return MyService.get({userId: id});
}];
}

AngularJS+ Firebase can't get data preloaded on route change

var testApp = angular.module('testApp', ['firebase'])
.config(['$routeProvider','$locationProvider',function
($routeProvider,$locationProvider)
{
$routeProvider
.when('/', {
templateUrl: '/views/main.html',
controller: 'MainCtrl'
})
.when('/test', {
templateUrl: '/views/test.html',
controller: testCrtl,
resolve:
{
firedata: function($q,angularFire){
var deffered = $q.defer();
var ref = new Firebase('https://shadowfax.firebaseio.com/items');
ref.on('value', function(result){
deffered.resolve(result.val());
});
return deffered.promise;
}
}
})
.otherwise({
redirectTo: '/'
});
// $locationProvider.html5Mode( true );
}]);
angular.module('testApp')
.controller('MainCtrl', ['$scope','$routeParams','$rootScope', function ($scope,$routeParams,$rootScope) {
$scope.load = function(){ return false;}
$rootScope.$on('$routeChangeStart', function(next, current) {
$scope.load = function(){ return true;}
});
}]);
testApp.controller('TestCtrl',['$scope','$timeout','Fire','firedata','testCrtl']);
var testCrtl = function ($scope,$timeout,Fire,firedata) {
$scope.items=firedata;
};
In the code above, why is the value of $scope.items=firedata; null? Please explain how can I perform a Google-like route change to preload data for the controller? This example works like John Lindquist explains, but when I use Firebase's native JS library, I can't get the data preloaded.
Also, using the Firebase angularFire library doesn't help, because it uses $scope as a parameter and it's not possible to pass $scope to the resolve function.
You should be able to use angularFireCollection to preload data:
.when('/test', {
templateUrl: '/views/test.html',
controller: testCrtl,
resolve: {
firedata: function(angularFireCollection){
return angularFireCollection('https://shadowfax.firebaseio.com/items');
}
}
})

Resources