This is going to be difficult to explain but I'll try.
I am using UI-router in an angular app and would like t use the following URLS:
/contacts
/contacts/{id}
When you visit the /contacts page it will get a list of contacts from the server and display them. When you go to /contacts/1 it will get the contact 1 record from the server and show it.
My code currently looks like this:
.state('contacts', {
url: "/contacts",
templateUrl: "templates/contacts.tpl.html",
controller: "ContactsCtrl"
})
.state('contacts.contact', {
url: "/{contactID}",
templateUrl: "templates/contact.tpl.html",
controller: "ContactCtrl"
})
So far so good. but when you go to the second URL the parent is also activated so it's going to the server to get the list of contacts, even though they're not displayed, which is a waste.
I could set /contacts to "abstract:true" and use /contacts/list as the first URL, but that's not the URL I want to use and I do need to set a controller on the parent because I do have some logic I want to put in the parent (creating the navigation for that section).
Ideally, when the user hits /contacts I'd like the parent state to activate (to create the navigation) and run a default child state to list the contacts without redirecting to another URL. If the user goes to /contacts/8 then It would still activate the parent state but not the default state so it never goes to the server to get the contacts.
I hope that makes sense. I've not been able to create a plunkr, but the Angular UI guys kindly created one which shows the imperfect solution above.
http://plnkr.co/edit/gmtcE2?p=preview
I could set /contacts to "abstract:true"
That would be one part of the correct approach. A parent state should not load data that doesn't apply to a child, but your state tree doesn't have to reflect your URL structure exactly. For example:
.state('contacts', {
abstract: true,
url: "/contacts",
/* Various other settings common to both child states */
})
.state('contacts.list', {
url: "", // Note the empty URL
templateUrl: "templates/contacts.tpl.html",
controller: "ContactsCtrl"
})
.state('contacts.item', {
url: "/{id}",
templateUrl: "templates/contact.tpl.html",
controller: "ContactCtrl"
})
Related
I have define state for my shop application but I'm not sure I'm doing it right. Since I have more than one optional parameter in url I'm not sure how should I implement it.
.state('app.products', {
abstract: true,
views: {
'content#': {
templateUrl: 'app/products/views/product.html'
},
'productHeader#app.products': {
templateUrl: 'app/products/views/product-header.html'
}
}
})
Above is my abstract view for products page. Products will be separated in man/women and also subcategories like:
www.example.com/man/
www.example.com/man/footwear/
www.example.com/man/footwear/shoes
Man, footwear and shoes are all optional since man param can be woman, footwear can be cloth (where last param would be e.g. shirts) and all possible combinations of those above.
I'm not sure if I have to make every state separately or I can handle all this with one more state except this one?
Just to note, product header is not relevant here and if its required for good structure to remove it, surely I can do that.
I just can't find anything similar online, so link would be also be helpful if anyone has any.
I've done something very similar recently by nesting each subcategory state into its parent category state. Some benefits of doing it this way are that you save yourself from having to repeat a lot of code in a child state that was already defined in a parent state, and also from having to reload data and views that were already loaded in the parent state.
Here's an example to get you started:
.state('app.products', {
abstract: true,
url: '/products',
views: {...}
})
.state('app.products.gender', {
url: '/:gender',
views: {...}
})
.state('app.products.gender.category', {
url: '/:category',
views: {...}
})
.state('app.products.gender.category.type', {
url: '/:type',
views: {...}
})
First, the urls automatically stack in child states. This means you only have to define one url parameter per child state and you still wind up getting urls like this /app/products/:gender/:category/:type.
The second benefit of doing it this way is that views defined in a parent state are automatically included in all of its child states, unless you explicitly override it:
.state('app.products.gender.category', {
url: '/:category',
views: {
'foo#someParentState': {templateUrl: 'foo.html'},
'bar#someParentState': {templateUrl: 'bar.html'}
}
})
.state('app.products.gender.category.type', {
url: '/:type',
views: {
// foo.html is still rendered here
// bar.html is replaced by baz.html
'bar#someParentState': {templateUrl: 'baz.html'}
}
})
Another benefit seen from this example, is that foo.html won't be reloaded when the state changes to app.products.gender.category.type. For example, say foo.html has a long scrolling list of types in that category. If the user clicks on an item in the list that changes the state from app.products.gender.category to the child state app.products.gender.category.type, then foo's long scrolling list will not be reloaded, and the user can still see the item they clicked on. On the other hand, if that click had changed the state to a non-child state, then the list would probably have been reloaded (data and all), and the user might have to scroll to see the item they clicked on.
Some advice:
Keep your nested state names short.
Only include a state in the hierarchy if it's absolutely necessary (I'm looking at you app.products!).
There are plenty of ways that this technique can go wrong so be sure to review the ui-router docs for configurations that help you code less.
'use strict';
angular.module('cbApp')
.config(function ($stateProvider) {
$stateProvider
.state('search', {
url: '/college/search',
templateUrl: 'app/collegesearch/views/collegesearch.html',
controller: 'collegeSearchCtrl'
})
.state('searchCollegeFilter', {
url: '/college/search/:streamId?cities&courses&branches&ordering',
templateUrl: 'app/collegesearch/views/collegesearch.html',
controller: 'collegeSearchCtrl'
});
});
Here my application calls the 1st state i.e 'search' with a url /college/search. Inside the controller I transition to another state searchCollegeFilter.
What I wanna do is navigate the user back to the back they came from when they click the browser back button. Say they came from '/' I want them to go back to home page. But in browser back history there are 2 entries for college/search. I want this to happen only for the 1st time.
For this northing is do with angularjs, the thing is you need to watch browser back event before navigating "window.onhashchange". By observing that you can make you check and can redirect default page
What I am doing in a different application would serve the purpose:
Basically: Specify parent states!
-> Then the logic is becoming easy.
You don't have to be specific about history or back button or anything like that.
Basically:
-> Check in
$rootScope.$on("$onstateChange", ...
-> If
the fromState.parent equals toState.parent
then $window.history.replaceState({}, "My Detail State Page", $state.url(toState)
I'm still working on an angular app, using the great ui-router. I'd like to use some dynamic nested states (I talked about it here), so I know it's possible.
Here is my code, with a specific state and its dynamic children states :
.state('home', {
url: '/home',
controller: 'RouteCtrl'
})
.state('home.state', {
url: '/home/:state',
controller: 'RouteCtrl'
})
$urlRouterProvider.otherwise('/home')
I have 3 buttons (more specifically, I use <a> to make it) to access 3 differents states : 'home', 'contact' and 'about'. 'contact' and 'about' are 'home' nested states and every state has a specific text to display when activated.
Unfortunatly, it appears that both of the children states aren't resolved from 'home' state.
Here is a plunker of the problem which match with my problem. Any idea ?
This will give you a working call to the new states:
$scope.redirect = function(state) {
console.log('redirect to state : ' + state);
if (state != 'home') {
$state.go('home.state', {
'state': state
});
} else {
$state.go('home');
}
}
However, it still won't change the text on the page, because the controller only sets it once when initially loaded.
Technically home, contact and about are not 3 states. What you appear to be doing is altering the content based of the parameters of the state. This could be achieved using one state and forcing ui-router to reload the state when you use $state.go
I have modified your plunkr here http://plnkr.co/edit/XXaltjG17FwY15tSbKaD?p=preview
Your state definition could look something like this,
.state('home', {
url: '/home?state',
controller: 'RouteCtrl'
})
The question mark makes the state parameter optional and also a query string.
The redirection could look something like this. You need to reload as you are going to the same route
$state.go('home', {state: state}, {reload: true});
Redirecting to the home page could look something like this. You need to disable inheritance for this redirect as you don't want to keep the query strings.
$state.go('home',{}, {reload: true, inherit: false});
The main problem here is that you want to have a variable in the state. You can't go to state home.about since it's not a given .state.
You should look at stateParams, or you can specify the URL where you want to go to the URL with Angular's $location.
Note: I think the url for a child state like home.state does not need the /home URL since it's in the father's state.
I feel a bit silly for asking this, I've looked and searched for this for quite a few hours and know the two main methods for setting up default child states is to:
url: '',
or
$urlRouterProvider.when('/route', '/newroute');
I'm trying to use the empty url config as the reroute option causes unwanted issues when the parent state is set to abstract: true. All templates contain a ui-view element(s).
My code looks similar to this at the moment:
.state('data', {
url: "/data",
templateUrl: "views/data.php",
})
.state('data.overview', {
url: "",
templateUrl: "views/subviews/data.overview.php",
})
.state('data.overview.view', {
url: "",
templateUrl: "views/subviews/subviews/data.overview.view.php"
})
.state('data.overview.view.msgfav', {
url: "",
views: {
msg: {
templateUrl: "views/subviews/subviews/data.overview.view.msg.php"
},
fav: {
templateUrl: "views/subviews/subviews/data.overview.view.fav.php"
}
}
})
As well as some more child states (which didn't seem relevant to include since not meant to be default). I've tried with the parent state as abstract: true and without setting it, with url empty and no url setting at all, with only the closest child state and not all nested. Sticking an ng-include in there can work as default state, but it feels unclean.Targeting each child directly works fine. I'm really not sure what I'm missing here, but I'm sure it's some small detail that will make me look silly.
I'm not sure whether I should reply with an answer to my own question, but think that's appropriate.
I don't think you can target the parent state (abstract) directly in ui-sref and expect it to default to child state and populate the view, but rather target the child state in links. Then have to set every parent state down the chain to abstract down to the point you want populated automatically. I didn't find this completely clear, and one part of the answer in this post threw me off a little on that;
"First, add a property to the 'manager.staffDetail.view' state of abstract:true. This isn't required, but you want to set this since you'd never go to this state directly, you'd always go to one of it's child states."
angularjs ui-router default child state
I'm working on a simple angular application using ui-router. I have a couple states for selecting and then editing information about a publisher. The relevant config:
.state('select-publisher', {
url: '/select-publisher',
templateUrl: '/Content/superadmin/src/templates/publishers/publishers.html'
})
.state('publisher', {
abstract: true,
url: 'publisher/{id:int}',
templateUrl: '/Content/superadmin/src/templates/publishers/publisher.html'
})
.state('publisher.details', {
url: '/details',
templateUrl: '/Content/superadmin/src/templates/publishers/details.html'
})
.state('publisher.ad-tags', {
url: '/ad-tags',
templateUrl: '/Content/superadmin/src/templates/publishers/ad-tags.html'
})
.state('publisher.native-ads', {
url: '/native-ads',
templateUrl: '/Content/superadmin/src/templates/publishers/native-ads.html'
})
Inside the select-publisher state I have a big list of available publishers. Each one of them is bound to an ng-click event that triggers the following function in my controller:
$scope.selectPublisher = function(publisher) {
publisherService.setSelectedPublisher(publisher);
$state.go('publisher.details', {id: publisher.Id});
};
This works just fine and takes me to the publisher.details state and renders the proper view. At this point the URL in my browser points to localhost:1337/superadmin#/publisher/39/details where 39 is the ID of the publisher that I selected.
The problem is, if I refresh this page or attempt to navigate directly to it by pasting the URL into the browser from another area of the application, I am ALWAYS taken back to the select-publisher state. I would like to be able to configure my states such that I am able to navigate to the details state (or any other state) based on URL.
Worth noting is that I do have a catch all route defined after all of my states:
$urlRouterProvider.otherwise('/select-publisher');
I'm assuming that for some reason this is being triggered but I can't reason as to why navigation works in my app using either $state.go as I have indicated in my controller as well as using ui-sref directive in my HTML templates but not through navigating directly to the URL.
Maybe it's because of missing slash url: /publisher/{id:int}