instead of using ng.router i am switching to ui.router in my angularjs application.
I came across a problem about state names in config. When I use '.' character in state name, routing doesn't work. Code I gave below doesn't route to 'views/subcategories.html' when I enter "localhost/#/categories/5" in my browser url.
$stateProvider
.state('categories',
{
url:'/categories',
controller:'CategoriesController',
templateUrl:'views/categories.html'
})
.state('sub.categories',{
url:'/categories/:subID',
controller: 'SubCategoriesController',
templateUrl:'views/subcategories.html'
});
However when I use 'subcategories' instead of 'sub.categories' it works. I am asking this question because I saw state names with '.' in a lot of tutorials about ui.router. Do I miss some detail here?
with dot syntax in name of states, you can a nested states. The nested state, inherit params and urls from parent state.
First, you need start the name of nested state, with parent state:
'categories.sub' // instead 'sub.categories'
Then, to localhost/#/categories/5, set your nested state like below:
$stateProvider
.state('categories',
{
url:'/categories',
controller:'CategoriesController',
templateUrl:'views/categories.html'
})
.state('categories.sub',{
url:'/:subID',
controller: 'SubCategoriesController',
templateUrl:'views/subcategories.html'
});
Note, the state 'categories.sub', in url, dont need '/categories', because this is inherit from parent state 'categories'
In ui-router, periods are used to indicate nested states.
Using sub.categories would make the categories state a child of the sub state.
More info can be found here
Related
I'm using ui-router with Angular 1.6. The root state is the site language (/en, /nl, /de).
Most child states are the same for all languages. However, for some states the names of the child states are translated:
/en/english-state/common-state
/nl/dutch-state/common-state
/de/german-state/common-state
How can I make sure the middle state always matches the lang parameter (en/nl/de) in the parent state? I'd like to use ui-sref without providing the middle state; this should be filled automatically based on the language. When the middle state doesn't match the language in the parent state it should be changed automatically to match the language in the parent state.
I can't find an intuitive solution in the ui-router docs, but perhaps I'm missing something. Anyone done this before?
After a while, I've decided to solve it like this, which I think is the cleanest way to do it:
(Note: This is a simplified version. I've replaced functions with strings , omitted dependencies and removed lines, this is not-working code, it's about the idea).
Expose the language variable in the resolve part of the root state, something like this:
$stateProvider.state({
url: '/{lang:(?:en|nl|de)}',
name: 'root',
[ ... ]
resolve: {
language: function() { return $stateParams.lang }
}
In the child state, check if the URL matches the language:
$stateProvider.state({
url: '/{mySlug:(?:english-state|dutch-state|german-state|__my-state__)}',
name: 'root.child',
resolve: {
checkSlug: function () {
$timout(function() {
if (notMatchesLanguage($stateParams.mySlug, language)) {
var params = JSON.parse(JSON.stringify($stateParams));
params.mySlug = correctSlug;
$state.transitionTo($state.current.name, params, { notify: false });
}
}
}
}
Note the added slug in the mySlug list, __my-state_ _
Now in every ui-sref, or $state.go we can use __my-state_ as mySlug parameter. This strange slug won't match any language and will be replaced with the matching state slug.
Spent hours googling and combing through the docs angular ui-router docs to no avail...seems like this should be really basic.
The quick version
Looking for something that I imagine might look something like this
<a ui-sref="parent({param: value}).child">go</a>
or this
$state.go("parent.child", {parent:{param: value}})
- the idea being that param gets applied to parent, not child, resulting in a url such as /parent/value/child
The long version
THE SETUP:
Let's say I have a state that takes a parameter, projectId, like so:
.state('manage.project', {
url: '/project/:projectId',
templateUrl: 'fragments/projectTemplate.html',
controller: 'projectController'
}
...then way down the state tree, I have one of many child states that depend on on that parameter:
.state('descendantOfProjectState.grandchildState', {
url: '/projectInfo',
templateUrl: 'fragments/projectInfo.html',
controller: function($state){
var projectId = $state.params.projectId;
}
}
Normally, one would first visit the parent:
$state.go('manage.project', {projectId: 123})
At this point the URL would look something like
/manage/project/123/foo/bar/projectInfo
From there, navigating to a child state is as simple as
$state.go("manage.project.foo.bar.descendantOfProjectState.grandchildState");
and the projectId is accessible via $state.params by virtue of being in the URL - no need to configure every single child state to explicitly expect the projectId (wondering if this is my fatal flaw).
THE PROBLEM:
Now the need has arisen to jump to that grandchild state, without first visiting the parent state. That is, I want to go directly to
/manage/project/123/foo/bar/projectInfo
from within the app (so, I want to go to the state, not the url).
How can I specify the projectId parameter for the project state, when it's in the middle of the route I'm navigating to?
$state.go('manage.project.foo.bar.descendant.grandchild', {projectId: 123})
doesn't inform the the ancestor state that needs the id.
How can I, in one fell swoop, tell ui-router to go directly to that grandchild state while passing the projectId to manage.project state which is in the middle of the route?
I'm having one parent state that has two children's state inside that I'm going to show one state based on the URL.
Out of those two states one is having to parameters like param1 and param2, I have use params option of ui-router inside state definition.
State
$stateProvider.state('tabs.account', {
url: '/account',
views: {
'content#tabs': {
templateUrl: 'account.html',
controller: function($scope, $stateParams) {
//This params are internally used to make ajax and show some data.
$scope.param1 = $stateParams.param1;
$scope.param2 = $stateParams.param2;
},
}
},
params: {
param1: { value: null }, //this are optional param
param2: { value: null } //because they are not used in url
}
});
If you look at my route the params option is not really introduced inside the URL, that's why I'm considering then as optional.
Problem
Look at plunkr, I've shown two tabs Account & Survey,
Click on Survey tab, then add some data in the textarea which are shown.
Click on Go to Account that will pass those textarea values to
the other Account tab by doing ui-sref="tabs.account({param1: thing1, param2: thing2})" on the anchor
Now you will see the param1 & param2 values on html which has been assigned to scope from $stateParams
Now again Click on Survey tab, you will land on the survey page.
Just click browser back, you will notice that param value is not getting null.
Problem Plunkr
I believe you got what I wanted to ask, why the optional parameter value has not been store? as they have been a part of state.
I know I can solve this issue by below two solutions.
By creating one service that will share data between two views.
By adding parameter inside the state URL. like url: '/account/:param1/:param2', (But i wouldn't prefer this)
I already tried angular-ui-routers sticky states but that doesn't seems to work for me. What is the better way to this?
Is there any way by which I can make my use case working, Any ideas would appreciate.
Github Issue Link Here
I would move the params definition to the parent state, so as to share the optional state params between your two child states.
The child states will inherit the $stateParams from your parent, as such there is no real 'workaround' needed.
Simply inject $stateParams as per usual in your child controllers and you will have full access to the params being passed around. If you don't want to utilise the params in a specific child state, simply avoid injecting them.
This works with;
Back button
Forward button
ui-sref (without params (will keep as-is))
ui-sref (with params (will overwrite))
$stateProvider
.state('parent', {
params: { p1: null, p2: null }
})
.state('parent.childOne', {
url: '/one',
controller: function ($stateParams) {
console.log($stateParams); // { p1: null, p2: null }
}
})
.state('parent.childTwo', {
url: '/two',
controller: function ($stateParams) {
console.log($stateParams); // { p1: null, p2: null }
}
})
If you at any point want to clear the params while travelling within the state tree of parent, you would have to do so manually.
That would be the only real caveat I can see by using this solution.
I realise manual clearing may not be desirable in the case you present, but you haven't taken an active stand against it, as such I feel the suggestion has merit.
updated plunker
One workaround solution is to cache the state params and conditionally load them when entering the tabs.account state. UI Router state config actually lets you provide an onEnter callback for these types of "do something on entering the state" situations.
Here's the basic logic using localStorage as the cache, with working Plunker here:
When you enter the tabs.account state, check for your state params
If you have them, cache them to local storage
If you don't, load them from local storage into $stateParams
Here's an example code snippet for reference (taken from the Plunker):
$stateProvider.state('tabs.account', {
...
onEnter: ['$stateParams', '$window', function($stateParams, $window) {
if($stateParams.param1) {
$window.localStorage.setItem('tabs.account.param1', $stateParams.param1);
} else {
$stateParams.param1 = $window.localStorage.getItem('tabs.account.param1');
}
if($stateParams.param2) {
$window.localStorage.setItem('tabs.account.param2', $stateParams.param2);
} else {
$stateParams.param2 = $window.localStorage.getItem('tabs.account.param2');
}
}],
...
}
One caveat is that your params will persist indefinitely (e.g. across refreshes and sessions). To get around this, you could clear out the cache on application load like in app.run.
One last note is that in the Plunker, I'm accessing local storage directly (through the Angular $window service). You might want to use some AngularJS module - I've used angular-local-storage in production.
I believe that what you want to achieve is not possible without using one of the two solution you provided.
The browser back-button is just keeping the URL history. He have no clue about the ui-router internal states and will just force the URL to change.
Forcing the URL to change will trigger internal ui-router machine but unfortunately ui-router will see the URL change the same as if someone would have change the url by hand.
Ui-router will fire a new route change to the route pointed by the URL. That mean he doesn't know you wanted to go "back" and will just change state to the new one without any parameters.
Summary
Clicking on back button will fire a state change to a new state according to the URL instead of going back to the previous state.
This is why adding the params to the URL solve the issue. Since the URL is discriminatory you'll finally land on the state you wanted.
Hope it helped.
To me this sounds as X Y problem. There are suggested ways to make state params persistent whereas the problem is located out of this surface.
By definition of the question there is data that should be kept independent of states. So it is kind of global relative to states. Thus there should be a service that keeps it and controllers of both states maintain it as required. Just don't pass data that is out of one state's scope in state params.
Sticky states would fit this approach easily since it allows to keep DOM and $scope while another state is active. But it has nothing to do with state params when the state is reactivated.
I am using angularjs with angular ui routes and I have a problem with multiple urls for route.
I have a state called "bookDetails". Book have unique id and unique name and therefore can be accessed both using id or name:
/books/:bookName
/books/id/:bookId
Those two routes are actually the same state. But I can't use the same state for multiple routes. Therefore I have to split it to two states:
$stateProvider.state('booByName', {
url: '/books/:bookName',
templateUrl: '/templates/book-details.html',
controller: bookController
})
.state('bookById', {
url: '/books/id/:bookId',
templateUrl: '/templates/book-details.html',
controller: bookController
});
Although I have code duplication, I can tolerate this. The problem begins when I have another view related to book: bookReaders. bookReaders shows the user all the user that read this book.
The ideal thing is to have "bookDetails" state and "bookDetails.bookReaders" substate. But because I have multiple routes for book, I have to create another two states:
bookByName.bookReaders and bookById.bookReaders and duplicate the states as well.
What is the best practice for such thing? How can I prevent states duplication?
Are you using UI Router? If so, you can use $urlMatcherFactory and UrlMatchers instead of writing the absolute URL. Then, define one state and a matcher that will match on both ID and Name, setting the state params accordingly.
Fellas, Fellaaas.
I'm writing from my phone so please excuse me for anything misleading or unclear.
In my app there's a state that can be inherited from any other parent state, called "deals/:dealId:".
As of now, I have to define the same state over and over again for any other parent state available, so it could be accessible from any URL on the app.
For example: the parent state "dashboard" has a child state "parent.dashboard.deals", so is another state - "parent.lookup.deals", and so on.
While googling for a solution I found an example on plunkr using a parent state variable definition to create a wildcard'ed state environment:
.state(currentState+".deals").
"Boy oh boy", I thought to myself. That's exactly what I need. Well, not.
I was testing it and it on a static "currentState" variable and it worked just fine, thought all I had left to do was to dynamically change the currentState variable between parent states switchings.
It appears that when angular is generating the routes for the first time on loading, it takes the default "currentState" var as a string and defines that state static. So, even though I'm changing the default "currentState" var between route changes, the state is only available to the first generated state definition.
Thank you.
I've posted a feature request on GitHub, just in case I'm the first one in the need for this scenario - or there isn't a proper solution meanwhile for this problem:
github.com/angular-ui/ui-router/issues/1014
Anyhow,
I managed this problem by looping through the parent states and attaching the static child state to them, instead of defining them manually:
angular.forEach(states, function(stateOptions,stateName) {
$stateProvider.state(stateName, stateOptions.options);
if (stateOptions.defaultState){
var dealStateName = stateName+'.deals';
console.log(dealStateName);
$stateProvider.state(dealStateName,{
url: (stateName=='parent.dashboard') ? "deals/:dealId" : "/deals/:dealId",
views:{
'fsItem#parent':{
templateUrl: "/static/html/fsItems/dealNewD.html",
controller: 'dealCtrl',
}
}
});
}
});
The question isn't quite clear, but it sounds like you want a state variable(s) that persists between route changes. I'd suggest using a factory or service. Take a look at Sharing a Variable Between Controllers in AngularJS.