Angular UI-Router Multiple Named Views - angularjs

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.

Related

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!

Angular ui.router: loading controller only when child view is active

In my Angular app, I have the following sample route definition which uses nested views:
.state('mapping.project', {
authenticate: true,
url: '/:projectId/:projectName',
controller: 'ProjectCtrl as proj',
views: {
'': {
templateUrl: 'app/components/mapping/partials/project.html',
controller: 'ProjectCtrl as proj'
},
'details#project': {
templateUrl: 'app/components/mapping/partials/details.html'
},
'activityTypes#project': {
templateUrl: 'app/components/mapping/partials/activity-types.html'
},
'boundaryPartners#project': {
templateUrl: 'app/components/mapping/partials/boundary-partners.html'
},
'progressMarkers#project': {
templateUrl: 'app/components/mapping/partials/progress-markers.html'
},
'users#project': {
templateUrl: 'app/components/mapping/partials/users/users.html',
controller: 'projectUserCtrl as vm'
}
}
})
Each of these views is loaded within a tab, using Angular Material md-tabs: https://material.angularjs.org/latest/api/directive/mdTabs
The problem is, when I access the route mapping.project, the projectUserCtrl that you can see on my users#project view gets immediately called. I assume the same would be true for each individual controller that I have attached to my nested views.
I'd want to call these nested controllers only when the tab is active and that particular template loaded (in this case: users#project).
How do I do this?
You don't need all these child views, you only need 1, a container for the visible tab. Make separate states for each of the tabs, then route the child view accordingly when your "tabs" are clicked.
I'm not sure on the material design specifics, but in essence your tabs become just links ui-sref links.

Issue with UI router and variables no longer being in the $scope after transitioning from one to another state

I have three states: one abstract state and two concrete states inheriting from the abstract one. I am transitioning from one state to another and I noticed that the variables that were in the $scope in one state are no longer in $scope after I have transitioned to the other state: see $scope.signupForm.member.email below.
Can someone please advise?
My UI router configuration:
$stateProvider
.state('signup', {
abstract: true,
views: {
'#': {
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.success.html'
}
}
})
Relevant snippet from my controller:
signupService.signup($scope.signupForm)
.success(function () {
//TODO: issue with controller no longer being in scope: signupForm.member.email is not displayed in template
$state.go('signup.success');
});
My email input (from signup.form.html):
<input type="email" name="email"
placeholder="{{'SIGNUP_FORM_EMAIL' | translate}}"
ng-model="signupForm.member.email" ng-required="true"
ng-pattern="EMAIL_PATTERN"
class="form-control"/>
Where I try to display the email (from signup.success.html):
<div class="panel-body">
success!
check your email at: {{signupForm.member.email}}
</div>
edit 1:
If I pull up the controller one level - by putting it into the abstract state i.e. 'signup', then signupFormCtrl - the angular form controller - is undefined!
<form name="signupFormCtrl" ng-submit="signup()" novalidate>
edit 2:
This is what I tried:
.state('signup', {
abstract: true,
views: {
'#': {
controller: 'SignupCtrl',
templateUrl: 'signup/views/signup.html'
}
}
})
.state('signup.form', {
url: '/signup',
views: {
'#signup': {
templateUrl: 'signup/views/signup.form.html'
}
}
})
.state('signup.success', {
url: '/signup/success',
views: {
'#signup': {
templateUrl: 'signup/views/signup.success.html'
}
}
})
Thre is a working plunker
This is feasable with UI-Router built-in features. We will need to introduce controller for our base state:
.state('signup', {
abstract: true,
templateUrl: 'signup/views/signup.html',
controller: 'SignupBaseCtrl',
})
Inside of this controller we would define a Model inside of a $scope:
.controller('SignupBaseCtrl', ['$scope', function ($scope) {
$scope.signupForm = { member : { email : null }};
}])
And now, if we would work with a model like this:
{{signupForm.member.email}}
In any of our child states, we would be accesing the same model, the same reference object singupForm.
And how it is possible? how it is working? All is clearly explained here:
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.
You can also check: Controller from Parent Layout is not access by child views
Data from one scope can not be accessed from a different scope. try using the rootScope for data that is to be used across scopes use $root in templates as in {{$root.signupForm.member.email}} and $rootScope in controllers as in $rootScope.signupForm.member.email

Nested states or views for layout with leftbar in ui-router?

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

In ui-router, is it possible to have nested views within nested views?

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

Resources