I am using Angular UI-Router and need to have a single parent state that several other states can inherit the results of a resolve from. I have tried every solution found here on SO for this but NOTHING is working. My states look like this:
.state('transaction', {
abstract:true,
url: '/register/pos/:screen/:document_sid',
template:'<div ui-view></div>',
resolve: {
Document: //the thing I need to have all the children access
}
})
.state('transaction.view', {
url:'',
views:{
'':{
templateUrl: '/views/docs/pos-transaction-outer-partial.htm',
controller:'posdocumentController'
}
}
})
.state('transaction.edit', {
url: '/:mode',
views:{
'':{
templateUrl: '/views/docs/pos-transaction-outer-partial.htm',
controller:'posdocumentController'
}
}
})
.state('transaction.returns', {
url: '/returns',
views:{
'':{
templateUrl: '/views/pos-itemreturn-partial.htm',
controller: 'posItemReturnsController'
}
}
})
When I use $state.go('transaction.edit') it works perfectly. Inside the 'posdocumentController' I have a function that calls $state.go('transaction.returns') and I can see the route change and the partial is requested, but the view never changes. According to the docs for multiple named views the specified views should load in the parents ui-view that is contained within the template. Obviously, for me this is not happening and for the life of me I have not been able to figure out why. I've tried this both with and without the views option and get the same results. Can someone explain why and how I fix this?
In details is hidden... in very small detail, in this case.
I. Why not working?
Here is a plunker with broken navigation (exactly as described in the question above)
These would be the calls to the above states:
ui-sref
transaction.view ({ screen: 1 ,document_sid:2 ,mode:5 })
transaction.edit ({ screen: 1 ,document_sid:2 ,mode:5 })
transaction.returns ({ screen: 1 ,document_sid:2 ,mode:5 })
href
#/register/pos/1/2
#/register/pos/1/2/5
#/register/pos/1/2/returns
so, while these could seem as proper state calls, the last two, edit and returns, could be observed as:
// edit link result
params
{
"screen": "1",
"document_sid": "2",
"mode": 5
}
state
{
"url": "/:mode",
...
// returns link result
params
{
"screen": "1",
"document_sid": "2",
"mode": "returns"
}
state
{
"url": "/:mode",
Now it should be clear. The third link (to state returns) is in fact navigating to edit again. Why? because of this states definition:
.state('transaction.edit', {
url: '/:mode', // here we say :mode is param
...
.state('transaction.returns', {
url: '/returns', // and 'returns' here is already mapped... above as :mode
Check the not working solution here
II. How to make it working
There is a link to working plunker. The change made is:
firstly define returns and then edit state
.state('transaction.returns', {
url: '/returns', // 'returns' would be evaulated first, and used
...
.state('transaction.edit', {
url: '/:mode', // only other then returns values will be used here
Working solution is here
Other solution, maybe better, would be to use some regex... to narrow :mode values: Regex Parameters
'/user/{id:[^/]*}' - Same as '/user/{id}' from the previous example.
'/user/{id:[0-9a-fA-F]{1,8}}' - Similar to the previous example, but only matches if the id parameter consists of 1 to 8 hex digits.
'/files/{path:.*}' - Matches any URL starting with '/files/' and captures the rest of the path into the parameter 'path'.
'/files/*path' - Ditto. Special syntax for catch all.
Related
I have two states:
.state('a.b', {
url: "",
views: {
"b": {
template: require('./ab.html'),
controller: ABController
}
})
.state('a.b.c', {
url: "/:someid",
views: {
"c": {
template: require('./abc.html'),
controller: ABCController
}
})
with ab.html:
<div ui-view="c"></div>
When I navigate from state a.b to state a.b.c, I see the ABController being reconstructed while when I navigate from state a.b.c to a.b, ABContoller is not reconstructed.
What I really want to achieve is to not reconstruct ABContoller when navigating from a.b to a.b.c or from a.b.c to a.b. Is there a way to do this ?
So, the answer is that this is indeed possible and it is what happens by default because, as I was expecting it, ui-router does track carefully what states need to be destroyed/created based on the from and to states.
In this case, what triggered the extra construction is the fact that two of my states used the same parameter variable which forced ui-router to consider the common elements of the state path to be different.
Basically, the fix involved removing an unused/buggy "params" attribute from a state definition.
I have two states on my angular app.
First :
.state('profile', {
url: '/:user/:user_id',
templateUrl: 'modules/profile/templates/profile.html',
controller:'profileController',
showClose : 'false',
resolve: {
USERNAME : function($stateParams){
return $stateParams.user;
},
USERID : function($stateParams){
return $stateParams.user_id;
}
}
})
Second :
.state('sub-moments-everyday', {
url: '/moments-everyday/:moment',
templateUrl: 'modules/moments-everyday/templates/ME-submoments.html',
controller:'subMomentsEverydayController',
})
Whenever I typed a valid user and user_id on the profile state there are no problems. But whenever I type /moments-everyday/randomstring it seems like the ui-router is getting confused. It is using the controller and template for the profile state.
Please help. Thanks.
Have you tried checking the order in which the states are placed in the file? /:userid/:username checks for ANY URL that matches that pattern. That pattern contains two variable parameters so when you try and access /moments-everyday/:moments, it sees the /:userid/:username pattern and calls that state.
Try making sure you have your /moments-everyday/:moments pattern BEFORE the /:userid/:username pattern. UI-router starts at the top of the file and checks each pattern going down the file till it finds the first matching pattern.
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
I'm currently working on a small application that shows a 'drill down' type of menu. Unlike a TreeView my component only displays the current level.
There are only 3 levels: main, submenu, item. Drilling deeper (on the item) will result in a new state that corresponds to a detail view of the selected item.
I have only 2 states, the first is valid for any of the three levels. The second is the detail state:
$stateProvider.state('menu', {
url: '/menu/:submenu/:item',
templateUrl: 'src/menu/views/menu.html',
controller: 'MenuController',
controllerAs: 'MenuController',
resolve: {
data: function($stateParams, MenuService){
return MenuService.loadUri($stateParams);
}
}
}).state('menu.details', {
url: '/:selectedItem',
templateUrl: 'src/menu/views/menu-details.html',
controller: 'MenuDetailsController',
controllerAs: 'MenuDetailsController',
resolve: {
data: function($stateParams, MenuService){
return MenuService.loadUri($stateParams);
}
}
});
This solutions works correctly, however I'm a bit annoyed by the fact I need to define the resolvemethod twice. I am required to do this as $stateParams contains only the params of that exact state (excluding child states) and I need all parameters to form a valid URL to fetch the corresponding resource via the MenuService.
I know that state.params can be used to get all state params, however it doesn't seem to work in the resolve method: it contains the parameters of the previous state.
Does anybody know how I could fix this? Having a child state that should simply trigger a new view, whilst using the resolve method of the parent state with all state parameters?
If you think I'm completely misunderstanding how states work, then please go ahead and tell me :)
I have three route states - an abstract state, a state that shows the listing and a state that specifically shows a detail of one particular object.
My states are configured as follows:
.state('movies', {
url: "/movies",
template: '<ui-view ng-show="isLoaded" />',
abstract: true,
ncyBreadcrumb: {
skip: true
}
})
.state('movies.index', {
url: "?genres&tags&year&season&page&perpage",
templateUrl: "views/movies.html",
controller: 'MoviesViewController',
ncyBreadcrumb: {
label: 'Movies'
}
})
.state('movies.show', {
url: "/:id/:slug",
controller: 'MoviesItemController',
templateUrl: "views/movies/details.html",
ncyBreadcrumb: {
parent: 'movies.index',
label: '{{item.title_main}}'
}
})
I'd like to add the posibility of movies.index also beginning with a slash, so the URL can either be /movies or /movies/. However with my design, I have no idea how to do that.
I'm also not sure if I understand the concept behind abstract states correctly. I can either use this, or have movies.index be the parent state, in which case, how do I replace its content by the child's template? Also, I have noticed that since the parent controller also gets called, an unnecessary GET request is sent to my API, which can slow things down (especially since it's completely useless information). Can anyone give me a hand in this?
I understand that this use case is supported by $urlMatcherFactory.strictMode(false)
Take a look her
http://angular-ui.github.io/ui-router/site/#/api/ui.router.util.$urlMatcherFactory#methods_strictmode