Angular ui-router parallel view with child states - angularjs

I've been looking around and perhaps I'm missing something, but I've been totally unable to figure out how to get this working. Basically, I want a state with three parallel views. Lets call them header, body, and footer. Header and footer work just fine as simple parallel views, but I haven't been able to figure out how to automatically render the body child state, so that I can use it to manage other views.
app.js
.state('main', {
url: '/',
views: {
mainModule: { templateUrl: 'partials/main.html'},
"header#main": {
templateUrl: "partials/header.html",
},
"footer#orders": {
templateUrl: "partials/footer.html",
},
}
})
.state('main.body',{
url:'/',
template:"<p>Test!</p>"
})
main.html
<div ui-view="header"></div>
<div ui-view></div>
<div ui-view="footer"></div>
I have a feeling that the ui-view section of the html is not the way to go, and that the answer might have to do with abstract states, but thus far I haven't managed to get it working. Any help would be appreciated.
I have also attempted to reference a view as if it were a state, but that also rendered nothing.
This answer seems to come close, but I haven't been able to get what is suggested in the comments working.
I've looked at other questions that are layout related, but none of the solutions I've come across have worked for me. Thanks!

There is a working plunker, showing all the small adjustments described below in action.
Firstly, we must be sure, that our index.html, the root view, has the place for our 'mainModule' view template. So this is a snippet of the index.html:
<body>
<div ui-view="mainModule" ></div>
...
That, means, that our core view (one of views defined in 'main' state) will be properly injected into root view.
Now, our header and footer should both be using absolute names, but their suffix must be 'main' (not 'orders' like above for footer). That is saying to UI-Router: place them inside of views defined in this state (main.html)
Also, we can (as part of this 'main' state) define some default content of the "body". It could be let's say some list view...
.state('main', {
url: '/',
views: {
// it could be "mainModule" as well... so it needs its: ui-view="mainModule"
mainModule: { templateUrl: 'tpl.main.html'},
"header#main": {
...
},
// wrong absolute name
// "footer#orders": {
// we need the 'main' as well
"footer#main": {
...
},
// even the body, unnamed view could have some default
"#main": {
templateUrl: "tpl.list.html", // e.g. list
},
}
Next, we can defined few more states as children of the 'main'. They will (by default) use the unnamed view of the main. And what's more - replace the list view used above:
.state('main.body',{
url:'/body',
...
})
.state('main.detail',{
url:'/detail:/id',
...
})
Observe it here, it should give all the answers...

The name of the views used in the ui-view directive should match the view names defined in your route configuration section:
<div ui-view="mainModule"></div>
<div ui-view="header#main"></div>
<div ui-view="footer#orders"></div>
I'm not entirely sure if the "#" symbol will give you trouble - if it does, try removing it from the view names.

Related

Multiple named views with dynamic routing in angularjs

Edit: Here is the complete code at Plunker. Though I can not c anything in execution but same code working at local. However gives a console error though
It all works perfect. But due to :id in /news/:id/, i am getting jquery/angular errors in console which can not be tracked anywhere in my code
I can not c What i am doing wrong.
Edit: Solved plunker https://plnkr.co/edit/FWcuBgGpVdMj3CroFrYJ
First of all you are trying to use ui-router but you're including ngRoute script in your plunker. Change it to
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.1/angular-ui-router.min.js"></script>
Then everything should work fine!
I suggest you a few changes...
1. Use ui-sref instead of href because it's much easier to define
ui-sref="post({id:1})" which turns into href="#/news/1"
If you would like to change url some day, then you will have to just change your route file, not each href.
$stateProvider
.state('post', {
url: "news/:id"
or
$stateProvider
.state('post', {
url: "archive/:id"
or
$stateProvider
.state('post', {
url: "whatever/:id"
2. Use abstract state
In your example it's a way better to define abstract state which holds header, content and footer - it's a typical use case.
ui-router
Abstract States
An abstract state can have child states but can not get activated
itself. An 'abstract' state is simply a state that can't be
transitioned to. It is activated implicitly when one of its
descendants are activated.
Some examples of how you might use an abstract state are:
To prepend a url to all child state urls. To insert a template with
its own ui-view(s) that its child states will populate. Optionally
assign a controller to the template. The controller must pair to a
template. Additionally, inherit $scope objects down to children, just
understand that this happens via the view hierarchy, not the state
hierarchy. To provide resolved dependencies via resolve for use by
child states. To provide inherited custom data via data for use by
child states or an event listener. To run an onEnter or onExit
function that may modify the application in someway. Any combination
of the above. Remember: Abstract states still need their own
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:
"".
Here's a plunker which shows how I would do it.
https://plnkr.co/edit/5FvJaelyxdl5MuALt5VY?p=preview
Hope it helps.
Look at the documentation for ui router named views,
You can use following syntax for using multiple views
$stateProvider
.state('state',{
url: '',
views: {
'header': {
templateUrl: 'views/header.html',
controller: 'headerCtrl'
},
'content': {
template: '<div ui-view=" "></div>', //<-- child templates loaded to here
},
'footer': {
templateUrl: 'views/footer.html',
controller: 'footerCtrl'
}
}
})
.state('state.post', {
url: 'news/:id/:KeyWords'
templateUrl: 'views/post.html' //<-- This goes into content's ui-view
});
I'm guessing you want to keep the header and footer and change content views.
You can achieve this by making this state as parent to all other states
suppose
.state('main',{
abstract: true,
views: {
'header': ... ,
'content': {
template: '<ui-view></ui-view>',
}
'footer': ...
}
})
then all the child views will load their views in the ,
ex: in main.child etc, your template will load in the content's <ui-view></ui-view> tag
If you need to use a custom template depending on keywords you can do the following:
.config(['$routeProvider',
function($routeProvider, $routeParams) {
$routeProvider
.when('/news/:id/:keyWords', {
template: '<div ng-include="url"></div>',
controller: "exampleController"
})
then in the exampleController
function($routeParams, $scope) {
$scope.url = $routeParams.keyWords;
}

How to target specific ui-view elements when nested ui-views exist

Imagine some html as follows:
<body ng-app="blocksApp">
Some content goes here
<div ui-view="monty">
<div ui-view="dave">Aaa</div>
<div ui-view="pete">Bbb</div>
<div ui-view="steve">Ccc</div>
</div>
</body>
Using ui-router, is there any way to code a state that will set "dave" to a new snippet of html, whilst leaving everything else untouched.
e.g. I'd like to do this:
$stateProvider
.state('daveonly',{
url: "/dave",
views:{
'dave':{template:"Dave now has content"}
}
})
I can't get it to work. The reason I want to do this is that sometimes I'd like to replace 'Dave' with a partial update, other times I'd like to replace the entire 'monty' with a partial update. It seems that ui-router does not like having nested ui-views in the same snippet of html.
From one point of view I'd like to suggest:
move html code to '.tpl.html' files
use 'templateUrl' instead of 'template'
And check if the following is suitable for you:
$stateProvider.state("daveonly", {
views: {
"dave": {
templateUrl: 'daveonly.tpl.html',
},
"pete": {
templateUrl: 'pete.tpl.html',
},
"steve": {
templateUrl: 'steve.tpl.html',
},
}
});
Take a look at page1 and page2 for more details.
But from another point of view it could be more useful to use only one ui-view and to redesign current ui-views to become the appropriate directives with controllers/services: usage of directives with controllers/services could help to manage partial reload correctly and to write unit-tests.
Yes it can be done easily with the help of abstract states and yes you are correct ui-router doesn't like direct nested views directly but it works fine if the views are in any child template.
Now consider this main page(index.html)
<body ng-app="app">
<div ng-view=""></div>
</body>
Now this template which will appear in this unnamed view. (parent.html)
<h3>This is the parent template</h3>
<div ng-view="child1"></div>
<div ng-view="child2"></div>
Now the JS file
$stateProvide.state('home',{
url:'/',
abstract:true,
views:{
"":{
templateUrl:'parent.html'
}
}
})
.state('home.child',{
url:"",
views:{
'child1#home':{
templateUrl:'child1.html'
},
'child2#home':{
template:'Child2'
}
}
})
.state('home.child.child1',{
url:"child1#home.child",
views:{
'child1#home':{
templateUrl:'child1viewchange.html'
}
}
});
(Now the manipulation part)
(child1.html)
<button ui-sref="home.child.child1">Child</button>
Now child1viewchange.html pe jana padega and wo dekhne wali hai kaunsi kaisi thi/......
(child1viewchange.html)
<h3>Child1's view change</h3>
So now when we click on the button in child view1 the content in the first view changes and if we assign controllers then they can use them to control data.

Separating UI components and composing them in one view

I have read following two articles (https://scotch.io/tutorials/angularjs-best-practices-directory-structure | https://github.com/johnpapa/angular-styleguide) from which I was convinced to structure my angular project in this way:
-app
---components
------header
---------header.controller.js
---------header.view.html
------personaldata
---------personaldata.controller.js
---------personaldata.view.html
------history
---------history.controller.js
---------history.view.html
------app.module.js
------app.routes.js
---shared
-index.html
(and some more but not important for my question)
Basically I want to create independent components with own controllers, services, directives and so on to place them on multiple, different pages/views. I am currently thinking that the respective view.html files will just include ...lets say...a div tag with some pure html content. Additionally I have a "main page/view" where all these components should be placed on.
How can I achieve that with AngularJS?
My research here brought me to the angular-ui-router prject which allows multiple nested views. Is that really the way to go for. The examples did not seem promising so far as they are loading partials there and I dont see a controller for each partial/component being loaded.
Any help pointing me in the right direction would be greatly appreciated.
Hope the question is clear. A small example putting two of such components in an index.html would as well be great.
Thanks in advance.
Cheers
SLi
I really like this approach and using it on my projects also.
You have still options for composing htmls. You can either write directives for each components so every components can have independencies but the problem is you may want to share same data between two components so this can limit your movement and force you to write much more code then you expect.
The other way is as you mention using nested views feature of ui-router. Let me give you basic example for it...
$stateProvider
.state('app', {
url: '/',
views: {
'content-left': {
controller: 'Ex1Ctrl as vm',
templateUrl: 'example1.html'
},
'content-right': {
controller: 'Ex2Ctrl as vm',
templateUrl: 'example2.html'
}
'content-top': {
controller: 'Ex3Ctrl as vm',
templateUrl: 'example3.html'
}
}
})
As you see we need three ui-view in main html with the name I give on state. So basically your html should be like this.
<div class="row">
<div class="col-md-12" ui-view="content-top"></div>
</div>
<div class="row">
<div class="col-md-6" ui-view="content-left"></div>
<div class="col-md-6" ui-view="content-right"></div>
</div>

ui-sref not working in nested views

Homework I've done - reviewed almost 8-9 ui-sref related questions (how to, complex examples, etc.) on this site, the related plunks, Scotch.io, etc. I can't seem to figure why my code is not working. I've spent 6-7 hours debugging the app and I can't seem to find the issue.
My page is divided into
a) layout.html - this forces the bootstrap grid system
b) app-navbar - nav bar on top (code is cut-n-paste of bootstrap example for now...)
c) app-accordion - uses the ui bootstrap accordion to display n number of items
d) app-right - right side comprises some UI elements and a ui-grid
The ui-view for all elements render correctly ... i.e., the "index" state is being called in my controller. I added a couple of simple buttons in app-navbar.html just to debug...
<ul class="nav nav-pills">
<li role="presentation" class="active"><a ui-sref="view1">View1</a></li>
<li role="presentation"><a ui-sref="view2">View2</a></li>
</ul>
<div ui-view="view-test"></div>
here's a snippet of my controller code:
.state('view1', {
url: '/view1',
views: {
'view-test': {
templateUrl: 'view1.html',
controller: 'View1Ctrl'
}
}
})
When I hover over the buttons, I see that the .../view1 and 2 show up on the browser's footer but when I click them nothing happens. For now, the accordion still uses href but it stops working if I use ui-sref. Once I get past this, that's the next question ... how do you use ui-sref for controls like accordion?
Any ideas on what might be going on? The plunk: http://plnkr.co/edit/c89j3eFvYyguMt0QznAI
There is updated working plunker. I made 2 changes.
Firstly I upgraded your version to UI-Router 0.2.13 (fixes some issues, simply always use the latest)
Secondly, I added the missing piece in state defintion: who is my parent
.state('view1', {
url: '/view1',
parent: 'index', // here is explicit setting who is parent
views: {
'view-test': {
templateUrl: 'view1.html',
controller: 'View1Ctrl'
}
}
})
Check it here
EXTENDED, based on the comment below, with few cites from documentation
Methods for Nesting States
States can be nested within each other. There are several ways of nesting states:
Using 'dot notation'. For example .state('contacts.list', {}).
Use the ui-router.stateHelper to build states from a nested state tree. Courtesy of #marklagendijk.
Using the parent property with the parent name as string. For example: parent: 'contacts'
Using the parent property with the parent object. For example parent: contacts (where 'contacts' is a stateObject)
...
Parent Property using State Name String
Alternately, you can specify the parent of a state via the parent property.
$stateProvider
.state('contacts', {})
.state('list', {
parent: 'contacts'
});

Nested views without nested states

Following this tutorial: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views, i'm able to create an application with multiple views defined in a root template. I need to modify that scheme a bit by putting tabledata and graph to a child view called content. I want my views to look like that:
index.html
<body>
<div ui-view="header"></div>
<div ui-view="content"></div>
</body>
content.html
<div ui-view="tabledata"></div>
<div ui-view="graph"></div>
And my routes looks like that:
# ...
.state('videos',
url: '/videos'
templateUrl: 'content.html'
views:
'tabledata':
templateUrl: 'tabledata.html'
controller: '...'
'sidebar':
templateUrl: 'graph.html'
controller: '...'
)
However, when pointing my browser to /videos, tabledata.html and graph.html templates are not loaded to corresponding views. Everything works great though, if i'll put tabledata and graph views to index.html
I'm sure there is something really wrong with my code but i'm not able to figure out what exactly nor google anything up.
As far as I'm aware you can only have multiple ui-view's in multiple named views, i.e.when you explicitly declare a views property on your state definition. I'm not entirely sure what you're after, but if you would like to have control over where hese ui-views load their states then you can use an abstract state, from the link you provided:
Views override state's template properties
If you define a views object, your state's templateUrl, template and
templateProvider will be ignored. So in the case that you need a
parent layout of these views, you can define an abstract state that
contains a template, and a child state under the layout state that
contains the 'views' object.
This is what I suggest:
.state('videos',{
url: '/videos',
templateUrl: 'content.html',
abstract: true})
.state('videos.xyz',{
url: '/xyz',//you can leave this empty if you like
{
views:{
'tabledata':{
templateUrl: 'tabledata.html'
controller: '...'
},
'sidebar':{
templateUrl: 'graph.html'
controller: '...'
}
}
})
If you don't want that xyz appended to your url's, just pass in an empty string for the url property of the state videos.xyz. I use this approach all the time, let me know if it's what you're after.

Resources