Angular + ui-router implementing tabs - angularjs

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 -->

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: Update URL without view refresh

I have an Angular SPA that presents a variety of recommendation lists, and a Google Map of locations, based on different cuts of some restaurant data (see m.amsterdamfoodie.nl). I want each of these lists to have their own URL. In order for Google to crawl the different lists I use <a> tags for the offcanvas navigation.
At present the <a> tag causes a view refresh, which is very noticeable with the map.
I can prevent this using ng-click and $event.preventDefault() (see code snippets below), but then I need to implement a means of updating the browser URL.
But in trying Angular's $state or the browser's history.pushstate, I end up triggering state changes and the view refresh...!
My question is therefore how can I update a model and the URL, but without refreshing the view? (See also Angular/UI-Router - How Can I Update The URL Without Refreshing Everything?)
I have experimented with a lot of approaches and currently have this html
Budget
In the controller:
this.action = ($event) ->
$event.preventDefault()
params = $event.target.href.match(/criteria\/(.*)\/(.*)$/)
# seems to cause a view refresh
# history.pushState({}, "page 2", "criteria/"+params[1]+"/"+params[2]);
# seems to cause a view refresh
# $state.transitionTo 'criteria', {criteria:params[1], q:params[2]}, {inherit:false}
updateModel(...)
And, what is I think is happening is that I am triggering the $stateProvider code:
angular.module 'afmnewApp'
.config ($stateProvider) ->
$stateProvider
.state 'main',
url: '/'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
.state 'criteria',
url: '/criteria/:criteria/:q'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
One possible clue is that with the code below if I load e.g. http://afmnew.herokuapp.com/criteria/cuisine/italian then the view refreshes as you navigate, whereas if I load http://afmnew.herokuapp.com/ there are no refreshes, but no URL updates instead. I don't understand why that is happening at all.
This is an example of the way to go if I understand correctly:
$state.go('my.state', {id:data.id}, {notify:false, reload:false});
//And to remove the id from the url:
$state.go('my.state', {id:undefined}, {notify:false, reload:false});
From user l-liava-l in the issue https://github.com/angular-ui/ui-router/issues/64
You can check the $state API here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state
Based on our previous discussions, I want to give you some idea, how to use UI-Router here. I believe, I understand your challenge properly... There is a working example. If this not fully suites, please take it as some inspiration
DISCLAIMER: With a plunker, I was not able to achieve this: http://m.amsterdamfoodie.nl/, but the principle should be in that example similar
So, there is a state definition (we have only two states)
$stateProvider
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
},
'right#main' : { templateUrl: 'tpl.right.html',},
'map#main' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list#main' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'map' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
}];
This would be our main tpl.layout.html
<div>
<section class="main">
<section class="map">
<div ui-view="map"></div>
</section>
<section class="list">
<div ui-view="list"></div>
</section>
</section>
<section class="right">
<div ui-view="right"></div>
</section>
</div>
As we can see, the main state does target these nested views of the main state: 'viewName#main', e.g. 'right#main'
Also the subview, main.criteria does inject into layout views.
Its url starts with a sign ^ (url : '^/criteria/:criteria/:value'), which allows to have / slash for main and not doubled slash for child
And also there are controllers, they are here a bit naive, but they should show, that on the background could be real data load (based on criteria).
The most important stuff here is, that the PARENT MainCtrl creates the $scope.Model = {}. This property will be (thanks to inheritance) shared among parent and children. That's why this all will work:
app.controller('MainCtrl', function($scope)
{
$scope.Model = {};
$scope.Model.data = ['Rest1', 'Rest2', 'Rest3', 'Rest4', 'Rest5'];
$scope.Model.randOrd = function (){ return (Math.round(Math.random())-0.5); };
})
.controller('ListCtrl', function($scope, $stateParams)
{
$scope.Model.list = []
$scope.Model.data
.sort( $scope.Model.randOrd )
.forEach(function(i) {$scope.Model.list.push(i + " - " + $stateParams.value || "root")})
$scope.Model.selected = $scope.Model.list[0];
$scope.Model.select = function(index){
$scope.Model.selected = $scope.Model.list[index];
}
})
This should get some idea how we can use the features provided for us by UI-Router:
Absolute Routes (^)
Scope Inheritance by View Hierarchy Only
View Names - Relative vs. Absolute Names
Check the above extract here, in the working example
Extend: new plunker here
If we do not want to have map view to be recreated, we can just omit that form the child state def:
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
// 'map' : {
// templateUrl: 'tpl.map.html',
// controller: 'MapCtrl',
//},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
Now our map VIEW will be just recieving changes in the model (could be watched) but view and controller won't be rerendered
ALSO, there is another plunker http://plnkr.co/edit/y0GzHv?p=preview which uses the controllerAs
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
controllerAs: 'main', // here
},
...
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
controllerAs: 'list', // here
},
},
})
and that could be used like this:
<h4>{{main.hello()}}</h4>
<h4>{{list.hello()}}</h4>
The last plunker is here
you can use scope inheritance to update url without refreshing view
$stateProvider
.state('itemList', {
url: '/itemlist',
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
//abstract: true //abstract maybe?
}).state('itemList.itemDetail', {
url: '/:itemName/:itemID',
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail',
resolve: {
'CurrentItemID': ['$stateParams',function ($stateParams) {
return $stateParams['itemID'];
}]
}
})
if child view is inside parent view both controllers share same scope.
so you can place a dummy (or neccessary) ui-view inside parent view which will be populated by child view.
and insert a
$scope.loadChildData = function(itemID){..blabla..};
function in parent controller which will be called by child controller on controller load. so when a user clicks
<a ui-sref="childState({itemID: 12})">bla</a>
only child controller and child view will be refreshed. then you can call parent scope function with necessary parameters.
The short answer ended up being do not put the map inside a view that changes. The accepted answer provides a lot more detail on how to structure a page with sub-views, but the key point is not to make the map part of the view but to connect its behaviour to a view that does change and to use a Controller to update the market icons.

Confused about ui-router nesting

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.

Update single view where there are many in AngularJs and UI-Router

I am a n00b to Angular, and trying to get my head around how ui-routing works. I see the how the concept of multiple routes works, and how nested routes work. How would i do both? Or is this not good practice?
In the example bellow I have app/wh working fine, however when some goes to app/wh/toc/123 I want only the middle view to swap.
.state('app.wh', {
url: "/wh",
views: {
'left' : {
templateUrl: "static/partials/leftPane.html"
},
'middle': {
templateUrl: "static/partials/start.html"
},
'right' : {
templateUrl: "static/partials/rightPane.html",
controller: 'AsideCtrl'
}
}
})
.state('app.wh.toc', {
url: "/toc/:id",
views: {
'middle' : {
templateUrl: "static/partials/toc.html",
controller: function($scope, $stateParams, $state){
$scope.title = $stateParams.id;
}
}
}
})
I am guessing that you want your left and right panels to stay static while the middle content changes through the users actions. Personally I am not a big fan of using multiple routes, and instead simply use ng-include if I want to separate portions of a single view into their own files.
If you do this, then you only need to define your parent, and child state:
.state('app.wh', url: '/wh', abstract: true, controller: ...
Note that if you want separate controllers for left and right, you can use ng-controller in your views. Also If this state is common to multiple middle contents, then you might want to make it an abstract state, so that the user cannot actually load it (it only acts as a parent placeholder).
.state('app.wh.toc', url: /toc/:id ...
Then in your view:
<div ng-include="someUrlRefLeft"></div>
<div ui-view=""></div>
<div ng-include="someUrlRefRight"></div>

How to have global footer in separate views using angular-ui-router v0.2.5 (allows modules)

Sorry if the title of this is confusing.
I'm converting a template I purchased into an angular.js app.
I want to use different modules to organize the app.
I'm also using version 0.2.5 of angular-ui-router which allows routing with separate modules.
All is well except the template I'm using looks like this:
<div>Global Nav Bar</div>
<div>Content that changes with different states right below Nav Bar</div>
<div class="wrapsContentAndPushesToBottom">
<div>Content that changes with different states at
bottom of page thanks to parent div's class</div>
<div>Global Footer also on bottom of page due
to parent div's class</div>
</div>
I'm having a hard time getting that global footer to work because of that parent wrapping div.
Can someone help me get this to work?
UPDATE:
I can't get suggested ng-include to work with my plunkr example: http://plnkr.co/edit/dgNkHX
I also can't it working using a named view for the footer: http://plnkr.co/edit/BO8NDO
I think you're looking for ng-include. http://docs.angularjs.org/api/ng.directive:ngInclude
That will enable you to extract that global footer out to a separate file and just include it in your template.
<div ng-include src="'globalFooter.tpl.html'"></div>
Try something like this:
.state('root', {
abstract: true,
views: {
'#': {
},
'sideBar#': { templateUrl: 'views/sidebar.html', controller: 'SideBarCtrl' },
'header#': { templateUrl: 'views/header.html' },
'footer#': { templateUrl: 'views/footer.html' }
}
})
.state('main', {
url: '/',
parent: 'root',
views: {
'#': { templateUrl: 'views/main_content.html', controller: 'MainCtrl' }
}
})
This is working for me.. I have a global footer.

Resources