UI Router conflicting when using similar URL pattern - angularjs

I have the following 2 states defined in my app.config:
state('product-details', {
url: '/products/:productId',
templateUrl: '/pages/product-details/product-details.tmpl.html',
controller: 'ProductDetailsController'
})
and
state('product-register', {
url: '/products/register',
templateUrl: '/pages/product-register/product-register.tmpl.html',
controller: 'ProductRegisterController'
})
The problem I am facing is since both their URL patterns are similar, when I try to navigate to product-register state, the 'register' is interpreted by angularui-router as a productId and redirects me to product-details state instead.
After going through some similar questions on SO, I thought I would filter these URLs using regexes as explained here. But when I tried this, although the regex was matching properly in isolation, the state refused to change.
Then again after doing a bit of research I decided to merge the two states like this:
state('product-details', {
url: '/products/:param',
templateUrl: ['$stateParams', function($stateParams) {
if($stateParams.param === 'register') {
return '/pages/product-register/product-register.tmpl.html';
} else {
return '/pages/product-details/product-details.tmpl.html';
}
}],
controller: ['$stateParams', function($stateParams) {
if($stateParams.param === 'register') {
return 'ProductRegisterController';
} else {
return 'ProductDetailsController';
}
}]
})
This also does not seem to work as expected. What am I missing? Isn't this a very normal thing you would have to do?

Try to move 'product-register' state before 'product-details' state.
Actually you need to differentiate your state URL pattern or else angular will fire the first match it will find.

You should be able to specify types for your parameters to match only certain states.
In your case you could use:
url: '/products/{productId:int}'
There are lots of other examples on the ui router page:
https://github.com/angular-ui/ui-router/wiki/URL-Routing

Related

AngularJS ui router mandatory parameters

Based on the documentation, angularjs ui-router url parameters are by default optional. So is there a way to create mandatory parameters? Like when the parameter is missing or null it will not proceed to the page?
Hope you can help me.
Thanks
Use UI Routers resolve to check if route params are missing.
//Example of a single route
.state('dashboard', {
url: '/dashboard/:userId',
templateUrl: 'dashboard.html',
controller: 'DashboardController',
resolve: function($stateParams, $location){
//Check if url parameter is missing.
if ($stateParams.userId === undefined) {
//Do something such as navigating to a different page.
$location.path('/somewhere/else');
}
}
})
Above answer prasents incorrect usage of resolve that will fail when code is minified even if annotated. Look at this issue I've spent a lot of time debugging this, because ui-router#0.4.2 won't warn you that resolve should be object.
resolve: {
somekey: function($stateParams, $location){
//Check if url parameter is missing.
if ($stateParams.userId === undefined) {
//Do something such as navigating to a different page.
$location.path('/somewhere/else');
}
}
If you need minification, ngAnnotate

How to catch the invalid URL?

I am building a simple HTML site using Angular JS ui-router.
I have built a custom 404 page ('www.mysite/#/error/404'), to which the user is redirected if the user types a url like 'www.mysite/#/invalidUrl'. This functionality was achieved by the following code snippet:
$urlRouterProvider
.when('', '/')
.when('/home', '/')
.otherwise('/error/404');
And the following state snippet:
$stateProvider.state('404', {
parent: 'app',
url: '^/error/404',
views: {
'errorContent#main': {
controller: 'error404Controller',
templateUrl: 'js/general/templates/error404.html'
}
}
});
I am trying to capture the invalid URL requested by the user to be displayed to the user like below.
The URL you requested cannot be found
Request: www.mysite.com/#/invalidUrl
I have tried listening to '$stateNotFound' event, which only seems to be firing when a specific invalid state is requested. I've also tried to listening to events '$stateChangeStart', '$locationChangeStart', and '$locationChangeSuccess' but couldn't find a way to achieve this functionality.
Is this possible? Please, share your thoughts - thank you
Awesome! Thank you Radim Köhler. I don't know how I didn't land on that thread when I searched but that helped me get the answer.
I changed the answer in that thread to fit my scenario which I am going to leave below for reference to any other users.
$stateProvider.state('404', {
parent: 'app',
url: '^/error/404',
views: {
'errorContent#main': {
controller: 'error404Controller',
templateUrl: 'js/general/templates/error404.html'
}
}
});
$urlRouterProvider
.when('', '/')
.when('/home', '/')
.otherwise(function ($injector, $location) {
var state = $injector.get('$state');
state.go('404', { url: $location.path() }, { location: true });
});
So the magic change was the function defined for 'otherwise' option. I didn't change the URL of the 'error' state. Instead, I used the 'state.go' to activate state '404' and passed the location path as the state parameter.
Setting location=true will take you to the URL of the state, and location=false will not. Whichever way you prefer the invalid URL can be accessed easily now.

Cannot read property '#' of null when using $state in onEnter

I am get the following error from the angular-ui-router:
TypeError: Cannot read property '#' of null
...
TypeError: Cannot read property '#main' of null
...
I have done some research on what could be the cause of this error and have come to the conclusion that the problem lies with the the fact that I do a redirect when a condition is false through a $state.go(state) during an onEnter of another state.
Here are some discussions concerning the same issue on github:
https://github.com/angular-ui/ui-router/issues/1234
https://github.com/angular-ui/ui-router/issues/1434
I tried some of the suggestions, but they are not working. They don't seem to provide a solution, but just point out the problem, and according to this github discussion it should be resolved, but I can't seem to get it to work.
I am using AngularJS v1.2.24 and ui-router v0.2.11.
Using the following code (simplified):
.state('main', {
abstract: true,
url: '/main',
templateUrl: '/main/_layout.html'
})
.state('main.home', {
url: '/home',
templateUrl: '/main/home/home.html',
controller: 'HomeCtrl',
onEnter: function ($state)
{
if (condition === true){
$state.go('main.catch')
}
}
})
.state('main.catch', {
url: '/catch',
templateUrl: '/main/catch/catch.html',
controller: 'CatchCtrl'
})
Is there a way to get this to work? Or should I consider a completely different approach to achieve the same result?
P.S.: A possible fix is to do the following in the controller,
$scope.$on('$stateChangeSuccess', function(){
// conditional redirect here
}
But I don't want to do it in the HomeCtrl.
I have encountered a similar situation, and solved it this way:
.state('main.home', {
url: '/home',
templateUrl: '/main/home/home.html',
controller: 'HomeCtrl',
onEnter: function ($state)
{
if (condition === true){
$timeout(function() {
$state.go('main.catch')
)};
}
}
})
The trick is that you want to let the original state change finish rather than trying to redirect in the middle of the state change. The $timeout allows the original state change to finish, and then immediately fire off the change to the desired state.
well, I know it's a little bit late but I've been having this problem for a long time and I finally solved it by listening to the $viewContentLoading event:
$rootScope.$on('$viewContentLoading', function(){
if(!$state.$current.locals)
$state.$current.locals = {};
});

Getting A Route Parameter for Resolve

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 + "!"
}; }, // ... })

angularjs ui-router location path not recognizing the forward slahes

I'm using angular ui router / stateProvider. However I set up my url any parts after the second forward slash is ignored and it always sends to this state:
$stateProvider.state('board', {
url: "/:board",
views: {
'content': {
templateUrl: '/tmpl/board',
controller: function($scope, $stateParams, $location) {
console.log('wat')
console.log($location)
}
}
}
});
Which has only 1 forward slash. Even when I go to localhost/contacts/asdf The following state doesn't run.
$stateProvider.state('test', {
url: "/contacts/asdf",
views: {
'main': {
templateUrl: '/tmpl/contacts/asdf',
controller: function () {
console.log('this doesnt work here')
}
}
}
});
This is a console log of $location. As you can see $location only recognizes the last part of the url as the path. As far as I can tell that's wrong. It's missing the "contacts" right before it. Any url is interpreted as having only 1 part for the url and sends to the board state. How do I fix this. THanks.
Edit: found that this was caused by angular 1.1.5. Reverting back to 1.1.4 didn't have this.
This may be the cause: https://github.com/angular/angular.js/issues/2799 . Try adding a base href.
Looks like that fixed in AngularJS v1.0.7. Just tested it.

Resources