I currently have a root index.html with a single ui-view which gets replaced depending on which "page" the user is on, e.g. Books, Games etc. Taking the Books page as an example, this view has content that I'd like to display on all pages which are part of the "Books" space, but the central content will differ depending on if the user is on the Books "homepage" or looking at a specific book. To facilitate this, my books.html has a nested state which either includes books_list.html or books_detail.html.
The url structure I'd like to have is:
/books - Shows left/right sidepanels plus a list of books in the middle of the page.
/books/1 - Shows left/right sidepanels plus the details for the book with ID 1 in the middle of the page (list of books is not displayed).
How can I set up my states to have the books.html template AND books_list.html template in the nested view when navigating to /books, but have books.html AND books_detail.html when navigating to /books/1 ?
I'm currently getting round this problem by having a "home" sub-state, but this means that I have to have /books/home, and /books displays no central content so is currently useless.
.state('books', {
url: '/books',
templateUrl: CONFIG.static_url + '/html/books.html',
...
})
.state('books.home', {
url: '/home',
templateUrl: CONFIG.static_url + '/html/books_list.html',
...
})
.state('books.detail', {
url: '/:bookId',
templateUrl: CONFIG.static_url + '/html/books_detail.html',
...
})
I achieved what I needed by declaring an abstract state:
.state('books', {
abstract: true,
url: '/books',
templateUrl: CONFIG.static_url + '/html/books.html',
...
})
.state('books.home', {
url: '',
templateUrl: CONFIG.static_url + '/html/books_list.html',
...
})
.state('books.detail', {
url: '/:bookId',
templateUrl: CONFIG.static_url + '/html/books_detail.html',
...
})
This means that /books loads both 'books' and 'books.home' states, and /books/1 loads both 'books' and 'books.detail' states, which is what I needed.
I created working example here. This is following your needs, because there are only two levels of nesting.
books is a parent for both children
books.home fills the middle erea - and is NOT parent of books.detail
books.detail replaces the list view - but that means that its $scope (books.home) is lost
State definition:
.state('books', {
url: '/books',
views: {
'#' : {
templateUrl: 'layout.html',
},
'left#books' : { templateUrl: 'tpl.left.html',},
'right#books' : { templateUrl: 'tpl.right.html',},
},
})
.state('books.home', {
url: '/home',
templateUrl: 'list.html',
})
.state('books.detail', {
url: '/:bookId',
templateUrl: 'detail.html',
controller: 'DetailCtrl'
})
Check it here
But there is also a different approach, which I like more. The list view, is parent of its child, and therefore it could keep its $scope, while navigating among details. Similar stuff discussed here
maybe try it
$urlRouterProvider.otherwise('/');
.....
.state('index', {
url: '/',
templateUrl: CONFIG.static_url + '/html/index.html',
...
})
Related
I'm new to Ionic and AngularJS so I'm struggling with (hopefully) something simple. I have these two states:
.state('menu', {
url: '/mobile/beer',
controller: 'beerCtrl',
templateUrl: 'templates/beer-menu.html'
})
.state('detail', {
url: '/mobile/beer/:id',
controller: 'beerDetailCtrl',
templateUrl: 'templates/beer.html',
resolve: {
beer: function($stateParams, beerService) {
return beerService.findBeerById($stateParams.id)
}
}
})
The 'menu' state fetches all beers from a server and hand the list over to 'beer-menu.html' that contains an ion-list to display the list. When the user selects a beer in the list, the 'detail' states steps in to display more details about the beer.
It's all working but I want to improve it a bit by using an abstract state:
.state('beer', {
abstract: true,
url: '/mobile/beer'
})
.state('beer.menu', {
url: '',
controller: 'beerCtrl',
templateUrl: 'templates/beer-menu.html'
})
.state('beer.detail', {
url: '/:id',
controller: 'beerDetailCtrl',
templateUrl: 'templates/beer.html',
resolve: {
beer: function($stateParams, beerService) {
return beerService.findBeerById($stateParams.id)
}
}
})
It looks totally OK to me - esp because this is based on one of the 'Formulas' from the Ionic Framework website. Nevertheless the app stops working when I structure the states like that. I've tried to tweak it in various way but couldn't get it working. Do you see what's wrong?
As the UI-Router documentation you have to define also a template for abstract state 'beer':
Remember: Abstract states still need their own <ui-view/> for their children to plug into. So if you are using an abstract state just to prepend a url, set resolves/data, or run an onEnter/Exit function, then you'll additionally need to set template: "<ui-view/>".
See:
https://github.com/angular-ui/ui-router/wiki/Nested-States-and-Nested-Views#abstract-states
I have the following layout:
Sidebar and Headerbar will always be present though their content is context-specific.
I think there are two options here: nested states (sidenav > Headerbar > Content) or with views (if I understand that correctly). I'm still struggling to get my head wrapped around ui-router regardless of how many videos and articles I've read.
Clicking on Sidenav would load a state (or view) into Content and Headerbar would adjust its content based on whatever is loaded into Content.
My sense is that nested states seem like the simplest direct approach, particularly when thinking about inheritance.
Looking at it from another point of view, these seem like they could be siblings (although inheritance issues probably make me wrong). My inkling is that views would allow me more flexibility in the future with sub-items and such.
And of course ng-include and directives could play into this.
Being new to ui-router could someone slap me in the right direction? Where I'm stuck is loading the home view. I want my users to see their dashboard in the Content section once they log in. And then, how do I load new elements into Content as the user navigates from the Sidebar?
One way how to design scenario with 1) side bar, 2) action section and 3) main area could be like in this working example
Firstly the root state. Here is root state named 'index'. It is abstract and could do some resolve for us. It does not effect child state naming and does not extend the url (because is undefined)
$stateProvider
.state('index', {
abstract: true,
//url: '/',
views: {
'#' : {
templateUrl: 'layout.html',
controller: 'IndexCtrl'
},
'top#index' : { templateUrl: 'tpl.top.html',},
'left#index' : { templateUrl: 'tpl.left.html',},
'main#index' : { templateUrl: 'tpl.main.html',},
},
})
The first real state is list, and it inherits from parent but with an attribute parent: 'index', so the parent name is not effecting the state name.
Advantage is, that it could inherit lot of resolved stuff. Also, the root state could be loaded once, for all other parent states
.state('list', {
parent: 'index',
url: '/list',
templateUrl: 'list.html',
controller: 'ListCtrl'
})
This is the real power of UI-Router, because now we can see that child is injecting stuff into two places - 1) action section and 2) main area
.state('list.detail', {
url: '/:id',
views: {
'detail#index' : {
templateUrl: 'detail.html',
controller: 'DetailCtrl'
},
'actions#index' : {
templateUrl: 'actions.html',
controller: 'ActionCtrl'
},
},
})
This way, we can use named views and multi views in real world scenario. Please, never forget how the scope definition goes:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Check that all in action here
I just would like to share my experience. There is
similar Q & A: Angular UI Router - Nested States with multiple layouts
and a link to working plunker
The snippet of the state def:
$stateProvider
.state('index', {
url: '/',
views: {
'#' : {
templateUrl: 'layout.html',
controller: 'IndexCtrl'
},
'top#index' : { templateUrl: 'tpl.top.html',},
'left#index' : { templateUrl: 'tpl.left.html',},
'main#index' : { templateUrl: 'tpl.main.html',},
},
})
.state('index.list', {
url: '/list',
templateUrl: 'list.html',
controller: 'ListCtrl'
})
.state('index.list.detail', {
url: '/:id',
views: {
'detail#index' : {
templateUrl: 'detail.html',
controller: 'DetailCtrl'
},
}
In a nutshell, I do use the nesting approach.
It is similar to the "core example" available here http://angular-ui.github.io/ui-router/sample/#/. It is hierarchical (entity list / detail)
And what's more, I use the hidden supper root state:
check the details here Updating resolved objects in ui.router parent states
the examle link
which is handling security related stuff - once, and shared among all child states:
$stateProvider
.state('root', {
abstract: true,
template: '<div ui-view></div>',
resolve: {objectX : function() { return {x : 'x', y : 'y'};}},
controller: 'rootController',
})
.state('home', {
parent: "root",
url: '/home',
templateUrl: 'tpl.example.html',
})
.state('search', {
parent: "root",
url: '/search',
templateUrl: 'tpl.example.html',
})
Hope it does enlighten this a bit, because the power of UI-Router I see in multiviews, view nesting, scope inheritance and the logical state machine behind
I am using Angular UI Router , and I have setup two routes
One for all the content pages like /about, /terms etc
$stateProvider.state('sidebarPages.page', {
url: ':slug',
views : {
...
}
});
And now I want to add another for other pages like our-team
$stateProvider.state('sidebarPages.page', {
url: 'our-team',
views : {
...
}
});
The problem is that the second state is ignored when I go to page /our-team and the first one is executed instead which is :slug , and could accept everything.
Is there a way that I can create these two states, one for specific pages , and one that will accept everything and put it in slug param , and based on param I can then bring it from DB.
I created working plunker here. The order decides. Create states with known names, then the one with the slug:
// States
$stateProvider
.state('home', {
url: "/home",
templateUrl: 'tpl.html',
})
.state('other', {
url: "/other",
templateUrl: 'tpl.html',
})
.state('slug', {
url: "/:slug",
templateUrl: 'tpl.html',
})
;
Check it here
I am doing my routing with ui-router and have a nested view that loads up some menu options for the item that is selected. My question is, is it possible to have nested views within a nested view with ui-router?
The code here works:
.state('settings', {
url: '/settings',
views: {
'': {
templateUrl: './templates/settings.html',
controller: 'settingsCtrl'
},
'details#settings': {
templateUrl: './templates/details.html',
controller: 'detailCtrl'
}
}
})
But say I wanted to load something attached to the details#settings view. How would I go about that? I have tried
.state('settings', {
url: '/settings',
views: {
'': {
templateUrl: './templates/main/settings.html',
controller: 'settingsCtrl'
},
'detail#settings': {
url: '',
views: {
'': {
templateUrl: './templates/detail.html',
controller: 'detailCtrl'
},
'contact#detail#settings': {
templateUrl: './templates/contactpref.html'
}
}
}
}
})
I don't get any errors in the console on this, but it does break my original nested view. From what I have found the docs aren't really clear on if this is even possible.
It is not possible. Check the: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider
and section views:
an optional map<string, object> which defined multiple views, or targets views manually/explicitly.
Examples:
Targets three named ui-views in the parent state's template
views: {
header: {
controller: "headerCtrl",
templateUrl: "header.html"
}, body: {
controller: "bodyCtrl",
templateUrl: "body.html"
}, footer: {
controller: "footCtrl",
templateUrl: "footer.html"
}
}
That is it. That's what UI-Router will see and resolve for us. And why are not you provided with any error?
Because expect value of the map<string, object> is object. It is iterated for known properties (controller, templateUrl) - and any uknown is ignored, not used, not found... skipped
Solution:
After some experience with UI-Router I would suggest:
if there are some features related - place them in state
if there are some more features related, but only with some conditions - place them into child state.
That structure will bring many benefits, mostly: stable parent (not reloading always) and dynamic child - placing new and new content into some targets/anchors/ui-views whenever parameter is changed. Check:
Trying to have one subview call another subview using ui-router
I started building ionic app on top of the sidemenu starter app. The starter app has a base state 'app' which is abstract and all the sidemenu pages are children of the app for example app.search, app.browse, app.playlists etc.
I have similar hierarchy. However, I want the start page to be some other page, which means it is at the app level.
The states look like this:
$stateProvider
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'AppCtrl'
})
.state('join', {
url: "/join",
views: {
'menuContent' :{
templateUrl: "templates/join.html",
controller: 'joinCtrl'
}
}
})
.state('app.search', {
url: "/search",
views: {
'menuContent' :{
templateUrl: "templates/search.html",
controller: 'searchCtrl'
}
}
})
.state('app.results', {
url: "/results",
views: {
'menuContent' :{
templateUrl: "templates/results.html",
controller: 'resultsCtrl'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/join');
When I run the app, the url defaults to
http://192.168.1.4:8100/#/join
and shows a blank page. Obviously, the join.html is not blank. Also, the console.log messages in joinCtrl are not outputted.
I am not able to figure out why is it not loading the join page. When I change the otherwise to point to '/app/search', everything works.
Any idea what's going on? How do I load the initial page by default and then navigate to the 'app.search' state?
I would expect that because the app is abstract - it is there for a reason. To be parent/layout state. In its template should most likely live all other states.
If yes - check this working example I created to demonstrate that. What we need is to mark the join as a child of the app state. Then the 'menuContent' placeholder will be properly searched in the app template:
.state('join', {
parent: 'app',
url: "^/join",
views: {
'menuContent': {
templateUrl: "tpl.join.html",
controller: 'joinCtrl'
}
}
})
There is a working plunker
The definition url: "^/join", is there to support the idea, that the url defined like this:
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/join');
will work even for nested state (join is child of app). See:
Absolute Routes (^)
If you want to have absolute url matching, then you need to prefix your url string with a special symbol '^'.
This is just one way... we can do the similar stuff if the join is not nested state, but then it should target the unnmaed view '' instead of 'menuContent'