Confused about ui-router nesting - angularjs

I'm trying to find a way for one of my views to have multiple states. Let's say I have this template:
<body>
<div ui-view="overlay">
<div ui-view="content">
</body>
...and these routes:
$stateProvider
.state('base', {
url: '',
abstract: true,
views: {
'overlay': {
templateUrl: '/src/overlay.html'
}
}
})
.state('base.page1', {
url: '/page1/',
views: {
'content#': {
templateUrl: '/src/page1.html'
}
}
})
.state('base.page2', {
url: '/page2/',
views: {
'content#': {
templateUrl: '/src/page2.html',
}
}
});
This allows me to show the "overlay" view on page1 and page2.
The "overlay" view needs to be able to change its state without affecting anything else on the page. I can set up "overlay" as a parent state and add children to it, but I can't figure out a way to activate any of those overlay.child states from inside a base state without losing whatever was in the base state. Can anyone point me in the right direction?
edit: plunker here http://plnkr.co/edit/vPmNhVLZNI2fOAZZOHkg

Your question is not very clear, but maybe what you want is to make them nested?
<div ui-view="parent">
<div ui-view="child">
</div>
</div>
$stateProvider
.state('parent', {
url: "/",
views: {
'parent': {
templateUrl: '/view/parent.html'
}
}
})
.state('parent.child', {
url: "/",
views: {
'child#parent': {
templateUrl: '/view/child.html'
}
}
})
Or is this not what you are looking for?
Edit:
What about this solution
And if you want you can use variables for your current state so that you don't have to define the overlay states for all parent states in your routeProvider like shown in this dirty example:
Edit2:
What you actually want is parallel states. You can read here on the topic and then you find out it is not yet supported in ui-router. But as written in my comment below you can take a look at ng-switch if you don't really need the state variables, since you can create similar parallel behavior with that directive.

Related

Angular UI-Router Multiple Named Views

Desired Behavior
I'm using AngularJS and the Angular UI-Router.
I want to allow two child states to share a parent state.
I want the child states to fill a ui-view in the parent state's view with their own view.
I want one of the two child states to have three ui-view's in their view, each filled with views.
Attempt at a diagram:
Parent: <ui-view>
filled by
Child: <ui-view> <ui-view> <ui-view>
filled filled filled
Specific Details for My Situation
I have a state called category-details. Inside the view for this abstract state I have an unnamed ui-view. In one of the two child states (category-details.selected) I want to use multiple named views.
Current WORKING Strategy
Here is the abstract state. Very basic, but included for your reference.
.state('category-details', {
abstract: true,
data: {
pageTitle: 'Category Details'
},
templateUrl: "views/category-details.html",
})
In the category-details.selected state (the state which will have multiple named views), I set the unnamed ui-view of category-details.html to category-details-selected.html:
.state('category-details.selected', {
views: {
'': {
templateUrl: 'views/category-details-selected.html',
controller: 'CategoryDetailsSelectedCtrl'
}
}
})
Inside of the category-details-selected.html view I have three named ui-views:
<div ui-view="firstNamedView"></div>
<div ui-view="secondNamedView"></div>
<div ui-view="thirdNamedView"></div>
Finally, I define a state for setting these three ui-view's to meet the third part of my desired behavior:
.state('category-details.selected.aspect', {
url:"/category-details/:selectedCategory",
views: {
'firstNamedView': {
templateUrl: 'views/first-named-view.html',
controller: 'FirstNamedViewCtrl'
},
'secondNamedView': {
templateUrl: 'views/second-named-view.html',
controller: 'SecondNamedViewCtrl'
},
'thirdNamedView': {
templateUrl: 'views/third-named-view.html',
controller: 'ThirdNamedViewCtrl'
}
}
});
Why My Solution is Awkward and Suboptimal
Adding the category-details.selected.aspect state to set constant elements (the three ui-view's) of the category-details-selected view is unnecessary. It forces creating an extra state every time I want multiple named views.
What I've Tried
I believe I should be able to move the url and views of the category-details.selected.aspect state into the views component of its parent state (category-details.selected). This would look like:
.state('category-details.selected', {
url:"/category-details/:selectedCategory",
views: {
'': {
templateUrl: 'views/category-details-selected.html',
controller: 'CategoryDetailsSelectedCtrl'
},
'firstNamedView': {
templateUrl: 'views/first-named-view.html',
controller: 'FirstNamedViewCtrl'
},
'secondNamedView': {
templateUrl: 'views/second-named-view.html',
controller: 'SecondNamedViewCtrl'
},
'thirdNamedView': {
templateUrl: 'views/third-named-view.html',
controller: 'ThirdNamedViewCtrl'
}
}
})
This resulted in the unnamed ui-view being set correctly, but the three named ui-view's were not filled.
Since selecting the three named ui-view's was the problem, I then attempted to select them with absolute selectors described here instead. This did not fix the problem. I tried:
firstNamedView
firstNamedView#
firstNamedView#category-details.selected
(others of course)
Closing Remarks
Is what I'm imagining possible, is another way better, or is my current method the best? It boils down to assigning the child ui-views of a parent ui-view being set at the same time. I thought the last sentence was too confusing alone, so I included the entire example.
Please let me know if I can provide any more clarification such as versions. Thank you.
Abstract states need their own <ui-view/> for their children to plug into.
Parent state category-details is abstract state. Child state will need some reference ui-view to plug that state into. In your I believe the view /category-details.html does not any ui-view (as you have mentioned that category-details-selected.html contains the ui-view).
Try this:
.state('category-details', {
abstract: true,
data: {
pageTitle: 'Category Details'
},
templateUrl: "views/category-details-selected.html",
})
.state('category-details.selected', {
url:"/category-details/:selectedCategory",
views: {
'firstNamedView': {
templateUrl: 'views/first-named-view.html',
controller: 'FirstNamedViewCtrl'
},
'secondNamedView': {
templateUrl: 'views/second-named-view.html',
controller: 'SecondNamedViewCtrl'
},
'thirdNamedView': {
templateUrl: 'views/third-named-view.html',
controller: 'ThirdNamedViewCtrl'
}
}
})
Here, we are giving abstract view a template, which has ui-view in it, for child to populate.
Have a look at documentation of ui-router: Abstract State for more information.
EDIT: I had assumed that views/category-details.html does not contain any ui-view. However, it was then pointed out that, views/views/category-details.html does have ui-view
This is what works for me:
category-details.html:
<div ui-view=""></div>
category-details-selected.html:
<div ui-view="firstNamedView"></div>
<div ui-view="secondNamedView"></div>
<div ui-view="thirdNamedView"></div>
router:
.state('category-details', {
abstract: true,
data: {
pageTitle: 'Category Details'
},
templateUrl: "../app/atest/category-details.html",
})
.state('category-details.selected', {
url: "/atest",
views: {
'': {
templateUrl: "../app/atest/category-details-selected.html",
// controller: 'approvalsCtrl as vm',
},
'firstNamedView#category-details.selected': {
templateUrl: '../app/atest/first.html',
// controller: 'approvalsCtrl as vm',
},
'secondNamedView#category-details.selected': {
templateUrl: '../app/atest/second.html',
// controller: 'approvalsCtrl as vm',
},
'thirdNamedView#category-details.selected': {
templateUrl: '../app/atest/third.html',
// controller: 'approvalsCtrl as vm',
}
}
})
I could see that you have mentioned you tried out using firstNamedView#category-details.selected, but it didn't worked for you. The above example is working for me. Check if you category-details.html and category-details-selected.html view contain proper ui-view.

Angular ui-router subview issues

I've been working with Angular for a year or 2 now, but this is my first project using ui-router. I'm running into a few issues with views and sub-views. The app is a standard left-side menu bar, with the views on the right changing depending on what's clicked in the menu bar.
On index.html
<body>
<div ui-view></div>
</body>
In the config.js file, which defines the routes
.state("dashboard", {
url: "/dashboard",
templateUrl: "components/dashboard/dashboard.html",
data: {
pageTitle: "Dashboard",
requiresLogin: false
}
})
.state("dashboard.welcome", {
url: "/welcome",
templateUrl: "components/welcome/welcome.html",
data: {
pageTitle: "Welcome",
requiresLogin: false
}
})
In the dashboard.html file
<div class="dashboard">
<div class="container-fluid">
<div class="row">
<div class="col-xs-12 col-md-8">
<div ui-view>
The /dashboard path loads correctly, and will load the left-side navigation bar with a blank right side. But changing the state to dashboard.welcome (/welcome) will not load the welcome.html template.
Whenever working with ui-router you need to understand that the concept of states is different from routes. When you define a sub-state, its defined relative to its parent state. In your scenario dashboard.welcome is defined as a child state of dashboard. The routes to substate is relative to the parent and is {parent url}/{child url}. Hence you should use either of the below 2 to route to that state:
Using $state.go change the state by specifying state name
$state.go('dashboard.welcome');
Using $location.path change the route by specifying url
$location.path('/dashboard/welcome');
It sounds like you want links to /welcome to be for state dashboard.welcome. Here is a plunker showing how this can be done. I show two sets of dashboard and welcome states. The first set of states (dashboard & welcome) shows that /dashboard/welcome will bring you to the dashboard.welcome state.
The second set (dashboard2 & welcome2) shows that /welcome will go to state dashboard2.welcome2. I believe this is what you were looking for.
If you hover over the links you can see where they will take you.
https://plnkr.co/edit/AVKPFa?p=info
Nested routes in ui-router get nested urls. I would however recommend using named-views for this kind of structure. You can find more info about it here:
https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
The gist of it is: you can specify a named component (ui-view) for your left menu navigation and another one for content, which gives you much more control down the line, because named components can be overwritten in child states or they can keep the default template, depending on your needs.
Example:
.state('root', {
url: '',
abstract: true,
views: {
'header': {
templateUrl: 'templates/partials/header.html',
controller: 'headerCtrl'
},
'logo': {
templateUrl: 'templates/partials/logoView.html'
},
'footer':{
templateUrl: 'templates/partials/footer.html',
controller: 'footerCtrl'
}
}
})
.state('root.login', {
url: '/login',
views: {
'header#': {
template: ''
},
'container#': {
templateUrl: 'templates/login.html',
controller: 'loginController'
}
}
})
.state('root.report', {
url: '/report',
views: {
'container#': {
templateUrl: 'templates/eu_dashboard.html',
controller: 'reportController'
}
}
})
And in your index.html:
<div ui-view="logo"></div>
<div ui-view="header"></div>
<div id="mainView" ui-view="container"></div>
<div ui-view="footer"></div>

What's the purpose of adding the extra 'views:{'foo':{}}' when declaring a view in angular?

What's the purpose of declaring the a view with views:{} e.g.
.state('app.example', {
url: "/example",
views: {
'my-example': {
templateUrl: "views/example.html",
controller: 'myCtrl'
}
}
})
as opposed to this
.state('app.example', {
url: "/example"
templateUrl: "views/example.html",
controller: 'myCtrl'
}
})
It is used for having multiple parallel views for a single state. Example:
.state('app.example', {
url: "/example",
views: {
"example1": {
template: 'app.example.view1'
},
"example2": {
template: 'app.example.view2'
}
}
})
index.html
<ui-view = "view1">
</ui-view>
<ui-view = "view2">
</ui-view>
So effectively, your state has two parallel views. You can find the detailed explanation here.
It is used to show multiple views on a single page(state).We use the above method when we show multiple parallel views on a single page.Suppose you have a page in which you have to show different charts on different positions,we created multiple views and appended the chart on those positions.Hope this helps
Ok I figured it out and I created a codepen for this to get it to fit nicely inside other ionic app. This updates the child view when a button or a sidemenu item is pressed with more detail inside the codepen by making multiple child views share a name like so
views: {
'shared-child-view' :{
templateUrl: "[path to your children, in our case child1.html and child2.html]"
}
}`
where it would look something like this
.state('sidemenu.parent.child1', {
url: "/child1",
views: {
'shared-child-view' :{
templateUrl: "child1.html"
}
}
})
.state('sidemenu.parent.child2', {
url: "/child2",
views: {
'shared-child-view': {
templateUrl: "child2.html"
}
}
})
`
Where it can sit in a parent that sits in an abstract state like this (but it doesnt have to but It's likely that this is how your ionic app will be setup):
.state('sidemenu', {
url: "/sidemenu",
abstract: true,
templateUrl: "sidemenu.html"
})
.state('sidemenu.parent', {
url: "/parent",
views: {
'menuContent' :{
templateUrl: "parent.html"
}
}
})
You can alternate or change each child view inside a view to evrey view with the same name, in this case "shared-child-state"
<div ui-view name="shared-child-view"></div>
and it can be made clickable with
<a href="#/sidemenu/parent/child2" class="item">Child View 2
</a>
This doesnt work if you use ui-serf.
I hope this helps someone!

UI Router: Multiple Independent Views

I feel like this is a straight forward use case for the ui-router but maybe I'm missing something...
I want to have two separate views next to each other controlled by their own menus. When I click a ui-sref link on one menu (or $state.go for that matter), I would like to update only one of the views. Additionally, only one of the two views needs to be reflected in the url.
I tried defining a few states:
$stateProvider
.state('home', {
url: '/',
views: {
'viewA': {
template: "I'm number A!"
},
'viewB': {
template: "It's good to be B."
}
}
})
.state('shouldOnlyChangeA', {
'url': '',
views: {
'viewA': {
template: 'Check out my new shoes!'
}
}
})
.state('shouldOnlyChangeB', {
'url': '/shouldGoToNewUrl',
views: {
'viewB': {
template: "This probably won't work..."
}
}
});
Now press $state.go('shouldOnlyChangeA') from your favorite controller and watch it change the crap out of viewB. I'd also like to omit the url definition in this state since the url should only change between the first and third states I've defined.
I have each ui-view sitting next to each other in index.html:
...
<div ui-view="viewA"></div>
<div ui-view="viewB"></div>
...
TL;DR
I want two sibling ui-views to be stateful all on their own; changing one shouldn't necessarily effect the other.
Hopefully I'm just missing something so I didn't bother to throw a plunker together or anything, but if it's more complicated and folks are willing to fiddle I'll whip something up.
See this similar question: Independent routing for multiple regions in an AngularJS single page application
I wrote UI-Router Extras - sticky states to accomplish this use case.
View the demo
Check out the demo source code for details.
I wrote UI-Router Extras - sticky states to achieve your goal.
You'll want one named <div ui-view='name'></div> for each region. Then, add sticky: true to the state definition which targets that region's named view.
See this plunk: http://plnkr.co/edit/nc5ebdDonDfxc1PjwEHp?p=preview
<div ui-view="viewA"></div>
<div ui-view="viewB"></div>
...
$stateProvider
.state('home', {
url: '/'
})
.state('shouldOnlyChangeA', {
'url': '',
sticky: true, // Root of independent state tree marked 'sticky'
views: {
'viewA#': {
template: 'Check out my new shoes!<div ui-view></div>'
}
}
})
.state('shouldOnlyChangeA.substate', {
'url': '/substate',
template: 'Lets get some shoes!'
})
.state('shouldOnlyChangeB', {
'url': '/shouldGoToNewUrl',
sticky: true, // Root of independent state tree marked 'sticky'
views: {
'viewB': {
template: "This probably won't work...<div ui-view></div>"
}
}
})
.state('shouldOnlyChangeB.substate', {
'url': '/substate',
template: "Golly, it worked"
}
);

Angular + ui-router implementing tabs

For the past 9 hours I have been trying to implement a page with tabs using Angular and ui-router.
I have the following:
.state('someAbstractParentRoot', {
'abstract': true,
url: '/{id}',
templateUrl: '/admin/templates/rootTemplate',
controller: 'someController1',
data: {
breadcrumbLabel: 'Details'
}
})
.state(someAbstractParentRoot.child, {
url: '',
views: {
details: {
templateUrl: '/admin/templates/details',
controller: 'someController2'
},
videos: {
templateUrl: '/admin/templates/videos',
controller: 'someController3'
},
logs: {
templateUrl: '/admin/templates/logs',
controller: 'someController4',
},
notes: {
templateUrl: '/admin/templates/notes',
controller: 'someController5'
}
}
})
Using bootstrap I simply show the template I need.
so when the videos tab is shown, the url is something like: localhost:8080/users/1#videos.
So, this all works great until I need to send this link to someone, because when I do that, the details tab is automatically opened. I can't just toggle classes on div elements because some of the tabs are using ng-if so jquery doesn't pick up all of the html it should, probably because of angulars digest cycle (it can be solved with setTimeout, but I am trying to avoid that).
Does anyone know how I can use ui-router to define a tab which will support direct linking?
Some example would be very much appreciated.
NB: I need the abstract parent state.
Can urls in ui-router have a '#' symbol?
There is a problem with your states
actually you declare only one state which can be divided in several parts you have to declare a state for each subviews so declare something like. I don't know if you really need an abstract state so i can juste declare:
$stateProvider.state('details', {
templateUrl: '/admin/templates/details',
controller: 'someController2'
})
.state('videos', {
templateUrl: '/admin/templates/details.html',
controller: 'someController3'
})
.state('logs', {
templateUrl: '/admin/templates/logs.html',
controller: 'someController4'
})
.state('notes', {
templateUrl: '/admin/templates/notes.html',
controller: 'someController5'
});
In your rootTemplate juste add this
<div id="tabsContainer">
<a ui-sref="details">Details</a>
<a ui-sref="logs">Logs</a>
....
</div>
<div ui-view> </div> <!-- Where your state template will be inserted -->

Resources