I have a state:
.state('series', {
url: '/{series}/',
template: '<div configurator-list></div>',
resolve: {
stateService: 'ConfiguratorStateService'
},
params: {
series: '{series}'
},
controller: 'ConfiguratorListController',
ncyBreadcrumb: {
label: '{series}',
parent: 'home'
}
});
I'd like to use actual value for {series} that is in the URL to update a few things. I'm lost and haven't had any luck searching. Everything takes me to the UI-router page, but I don't see any concrete examples there.
You need to listen for any of the UI-Router state change events and use the parameters from that function to obtain it. Add a listener in upon your app run (substitute app name for 'myApp') which is detailed below...
(function() {
angular.module('myApp')
.run(runOptions);
runOptions.$inject = ['$rootScope', '$state'];
function runOptions($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
if (toState.name === 'series') {
//output the parameter here... since the series parameter is actually the string of the url, use the url property
console.log(toState.url);
}
});
}
})();
There are other state change events you can use as well, depending on your intended use: https://github.com/angular-ui/ui-router/wiki#state-change-events
OR
What sounds more relevant with further reading of your question, modify your route definition...
Change url: '/{series}/' to '/:series'
Then, in your controller....
angular.controller('ConfiguratorListController', function($stateParams) {
var vm = this;
vm.series = $stateParams.series;
}
Related
I have an Angular endpoint like so:
$stateProvider.state("auth.signin", {
url: "/signin",
views: {
"auth-view#auth": {
templateUrl: "views/auth/signin.html",
controller: "SigninController"
}
}
});
I want a before filter (or a middleware) for my SigninController so if the user is already logged in, I want to redirect him to my HomeController.
In pseudo code, I'm looking for something like this:
$stateProvider.state("auth.signin", {
url: "/signin",
views: {
"auth-view#auth": {
templateUrl: "views/auth/signin.html",
controller: "SigninController"
}
},
before: () => {
if (User.loggedIn() === true) {
return $state.go("app.home");
}
}
});
I used similar features on alot of frameworks so I'm pretty certain Angular has something like this too. What's the Angular way of doing it?
Thank you.
Ps: I'm not using Angular 2 yet.
The ui router triggers a $stateChangeStart event, which you can capture:
app.run(["$rootScope", "$state", function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams, options) {
if (toState.name === "auth.signin" && userIsLoggedInLogicHere) {
event.preventDefault(); // prevent routing to the state
$state.transitionTo("app.home");
}
// else do nothing, it will just transition to the given state
})
}]);
See this documentation for reference
I am wondering if it is possible to change query parameter in the ui-router while a state is still being loaded.
I was thinking along the following lines but this does not work...
$stateProvider
.state('foo',{
url:'/foo?bar',
templateUrl:'app/foo.html',
controller: 'fooController as foo',
resolve: {
Resource: function($state) {
return somepromise()
.then(function(baz) {
if (baz !== some condition) {
$state.params.bar = 'newValue';
return;
}
});
}
}
Any Suggestion? Thanks...
You can handle it inside the state declaration (I prefer this way)
.state('foo', {
url: "/foo?bar", // not sure if this kind of url can be used
template: "",
controller: ['$state', function ($state, $stateParams) {
if ($stateParams.bar == 'something') {
$state.go('bar');
}
}]
})
Or you can redirect when the state is starting to change using event defined in ui-route
Normally I put these things in HomeController
$rootScope.$on('$stateChangeStart',
function (event, toState, toParams, fromState, fromParams) {
//console.log("state change:", fromState.name, fromParams, toState.name, toParams);
if (toState.name == 'foo' && toParams.bar == 'something') {
$state.go('bar');
}
});
Do beware this might trigger an infinite loop if not handled correctly.
I have a config file which contains a state that looks like the following:
$stateProvider.state('home', {
url: '?data',
abstract: true,
resolve: {
data: ['MyModel', '$stateParams', function (MyModel, $stateParams) {
return MyModel.getData($stateParams);
}]
}
});
and then a module file that looks like this:
App.module.run(['$rootScope', '$state', function ($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
where toParams would return object{data:undefined}. I know that the resolve works and should have data. How do I correctly pass the resolve from my state into the $stateChangeStart and then access it?
I have tried injecting my data resolve manually into my module file, but it returns undefined. I have tried setting the params attribute to see if that was the way to go:
resolve: {
data: ['MyModel', '$stateParams', function (MyModel, $stateParams) {
return MyModel.getData($stateParams);
}]
},
params: {
'foo':'bar', // this would be available in toParams
'data': //can i put my data here?
}
You should simply be able to inject MyModel into your run function, and read it the same way you read it inside your resolve process. Example run function (assumes your getData function returns a promise):
App.module.run(function($rootScope, MyModel) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
// get the same data your resolve is using (you can also pass in toParams or the like)
MyModel.getData(toParams).then(function(data){
// do something with your data here inside $stateChangeStart
});
});
});
This is what makes injection great, you simply inject something when you want to use it.
Plunker:
I am using UI-Router for a simple single page application. It is a just a table that you can sort and filter. When you apply a filter, like ordering a column in ascending order, the URL updates to something like:
www.domain.com#/column/'column name'/desc/false/
I then grab the paramters using $stateParams, and update the table accordingly.
$scope.$on('$stateChangeSuccess', function (evt, toState, toParams, fromState, fromParams) {
$scope.sorting.predicate = toParams.column;
$scope.sorting.reverse = boolVal(toParams.sort);
});
However, if I were to visit www.domain.com#/column/'column name'/desc/false/,
the table doesn't load with the data filtered. I am trying the following (after injecting $stateParams to my controller):
$scope.$on('$viewContentLoaded', function (event, viewConfig) {
console.log('VCL stateParams: ' + angular.toJson($stateParams));
});
However, stateParams is always empty regardless of the URL (only on viewContentLoaded). I can't seem to get the parameters of the URL on page load. Am I doing something incorrectly?
Here is the state config:
app.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$stateProvider.state("name", {
url: '/column/:column/desc/:sort/',
controller: 'Ctrl',
resolve: {
logSomeParams: ['$stateParams', '$state', function ($stateParams, $state) {
console.log($stateParams);
console.log(this); // this is the state you transitioned From.
console.log($state); // this is the state you transitioned To.
}]
}
});
$urlRouterProvider.otherwise('/');
}
]);
Here is how the state change is called:
<a ui-sref="name({column:'\''+column+'\'',sort:sorting.reverse})"
ng-click="sorting.predicate = '\'' + column + '\''; sorting.reverse=!sorting.reverse;">
{{column}}</a>
Here's a few options I'd investigate further.
Option 1: $rootScope listener
$scope.$root.$on('$stateChangeSuccess', function (evt, toState, toParams, fromState, fromParams) {
$scope.sorting.predicate = toParams.column;
$scope.sorting.reverse = boolVal(toParams.sort);
});
$scope.$root.$on('$viewContentLoaded', function (event, viewConfig) {
console.log('VCL stateParams: ' + angular.toJson($stateParams));
});
Option 2: Resolve objects
In your state definition, try to add this:
resolve: {
logSomeParams: ['$stateParams', '$state', function ($stateParams, $state) {
console.log($stateParams);
console.log(this); // this is the state you transitioned From.
console.log($state); // this is the state you transitioned To.
}]
}
Please give either of those a go and let me know what results you get.
The resolve object should run prior to $viewContentLoaded, but since all you are really interested in are the stateParams in your $viewContentLoaded callback, might aswell put it in the state definition.
Good luck,
Kasper.
Edit:
Try giving your state a parent state with the resolve object.
Like this:
$stateProvider.state('parentState', {
url: '',
abstract: true,
resolve: {
logSomeParams: ['$stateParams', '$state', function ($stateParams, $state) {
console.log($stateParams);
console.log(this); // this is the state you transitioned From.
console.log($state); // this is the state you transitioned To.
}]
}
});
$stateProvider.state("name", {
url: '/column/:column/desc/:sort/',
parent: 'parentState',
controller: 'Ctrl'
});
Let me know how that works out for ya.
Figured it out, quite unfortunate how simple the answer was. I needed to inject $state. I only injected $stateParams. You need both. Hope this saves someone some hair pulling and screaming.
I'm writing a handler for $stateChangeStart:
var stateChangeStartHandler = function(e, toState, toParams, fromState, fromParams) {
if (toState.includes('internal') && !$cookies.MySession) {
e.preventDefault();
// Some login stuff.
}
};
$rootScope.$on('$stateChangeStart', stateChangeStartHandler);
toState does not have the includes method. Should I be doing something different, or is there a way to do what I'm trying to do?
Also, when //some login stuff includes a $state.go(...), I get an infinite loop. What might cause that?
Here's a more complete example demonstrating what we eventually got to work:
angular.module('test', ['ui.router', 'ngCookies'])
.config(['$stateProvider', '$cookiesProvider', function($stateProvider, $cookiesProvider) {
$stateProvider
.state('public', {
abstract: true
})
.state('public.login', {
url: '/login'
})
.state('tool', {
abstract: true
})
.state('tool.suggestions', {
url: '/suggestions'
});
}])
.run(['$state', '$cookies', '$rootScope', function($state, $cookies, $rootScope) {
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
if (toState.name.indexOf('tool') > -1 && !$cookies.Session) {
// If logged out and transitioning to a logged in page:
e.preventDefault();
$state.go('public.login');
} else if (toState.name.indexOf('public') > -1 && $cookies.Session) {
// If logged in and transitioning to a logged out page:
e.preventDefault();
$state.go('tool.suggestions');
};
});
});
I don't like using indexOf to search for a particular state in the toState. It feels naive. I'm not sure why toState and fromState couldn't be an instance of the $state service, or why the $state service couldn't accept a state configuration override in its methods.
The infinite looping was caused by a mistake on our part. I don't love this, so I'm still looking for better answers.
Suggestion 1
When you add an object to $stateProvider.state that object is then passed with the state. So you can add additional properties which you can read later on when needed.
Example route configuration
$stateProvider
.state('public', {
abstract: true,
module: 'public'
})
.state('public.login', {
url: '/login',
module: 'public'
})
.state('tool', {
abstract: true,
module: 'private'
})
.state('tool.suggestions', {
url: '/suggestions',
module: 'private'
});
The $stateChangeStart event gives you acces to the toState and fromState objects. These state objects will contain the configuration properties.
Example check for the custom module property
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
if (toState.module === 'private' && !$cookies.Session) {
// If logged out and transitioning to a logged in page:
e.preventDefault();
$state.go('public.login');
} else if (toState.module === 'public' && $cookies.Session) {
// If logged in and transitioning to a logged out page:
e.preventDefault();
$state.go('tool.suggestions');
};
});
I didn't change the logic of the cookies because I think that is out of scope for your question.
Suggestion 2
You can create a Helper to get you this to work more modular.
Value publicStates
myApp.value('publicStates', function(){
return {
module: 'public',
routes: [{
name: 'login',
config: {
url: '/login'
}
}]
};
});
Value privateStates
myApp.value('privateStates', function(){
return {
module: 'private',
routes: [{
name: 'suggestions',
config: {
url: '/suggestions'
}
}]
};
});
The Helper
myApp.provider('stateshelperConfig', function () {
this.config = {
// These are the properties we need to set
// $stateProvider: undefined
process: function (stateConfigs){
var module = stateConfigs.module;
$stateProvider = this.$stateProvider;
$stateProvider.state(module, {
abstract: true,
module: module
});
angular.forEach(stateConfigs, function (route){
route.config.module = module;
$stateProvider.state(module + route.name, route.config);
});
}
};
this.$get = function () {
return {
config: this.config
};
};
});
Now you can use the helper to add the state configuration to your state configuration.
myApp.config(['$stateProvider', '$urlRouterProvider',
'stateshelperConfigProvider', 'publicStates', 'privateStates',
function ($stateProvider, $urlRouterProvider, helper, publicStates, privateStates) {
helper.config.$stateProvider = $stateProvider;
helper.process(publicStates);
helper.process(privateStates);
}]);
This way you can abstract the repeated code, and come up with a more modular solution.
Note: the code above isn't tested