September 29, 2015
Named ui-views with AngularJS, ui-router and ui-bootstrap
Tipps and Tricks
In one of my last tasks I have had the necessity of adapting a section of a single page application (SPA), in order to get ui-router and ui-bootstrap (plus Bootstrap CSS) work together.
In this article I will shortly share with you the result of my experience, by showing how to improve the coexistence of AngularJS, ui-router and ui-bootstrap via some “tricks” that can ease the creation and maintenance of your single page applications.
In particular, I will discuss how to manage the advanced injection of content in multiple views and, at the same time, how to handle the navigation within the application via the State Provider of ui-router. Such elements are not distant from what also the ui-router official guide describes and are aspects that you would need when switching from a basic to an advanced usage of the tool.
At the end of the article you will find a working example, containing what I will describe in the next paragraphs. I suggest you to firstly read the description, in order to understand the details of the example.
Let’s Go with ui-router
As is generally known, ui-router doesn’t comes bundled in AngularJS, instead it’s a separate tool that you can integrate in you project. The same for ui-bootstrap. In the following description I will presume that you are already able to move your first steps with both those libraries and that you know (more or less) the elements on which they are based on (for a good starting point to learn ui-router I suggest you this video).
Theory apart, the minimal set of elements that you need, to catch the meaning of the suggestions I want to share with you, is:
- An index.html page that you would use as starting point of your SPA (i.e. as container for some menu entries)
123456789101112131415161718192021222324<!DOCTYPE html><html ng-app="uiTabs"><!--..your dependancies...--><body ng-controller="MenuCtrl"><div class="container-fluid"><!--..bootstrap adaptive container...--><div class="row"><div class="col-md-2"><ul class="nav nav-list"><li ng-repeat="item in menu"><a ui-sref="{{item.state}}">{{item.name}}</a><!--..ui-sref links to a ui-router´s state instead of a simple href...--></li></ul></div><div class="col-md-8"><div ui-view></div><!--Each Element of the app will be injected here --><!-- ui-view is the element that a state will search in order to inject its view--></div></div></div></body></html> - An Angular Module where to inject the ui-router and ui-bootstrap dependencies
1var app = angular.module('uiTabs', ['ui.bootstrap', 'ui.router']); - A Controller for the index.html
123456app.controller('MenuCtrl', ['$scope', '$stateParams', '$state',function($scope) {$scope.menu = [{state: 'tabs', name: 'Example Tabs'},{state: 'content', name: 'Example Content'}];}]); - A Config for your App where you can define each state’s behavior
12app.config(function($stateProvider, $locationProvider, $urlRouterProvider) {});
Now, what you would normally do, in order to define the states associated with each menu entry is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$stateProvider. .state('tabs', { url: '/tabs', template: "<div id='container'><h1>{{voice}}</h1><br/><div ui-view></div> </div>", controller: function($scope, text) { $scope.voice = "Content example --tabs--"; } }) .state('content', { url: '/content', template: "<div id='container'><h1>{{voice}}</h1>{{text}}<br/><div ui-view></div></div>", controller: function($scope, text) { $scope.voice = "Content example --Content--"; $scope.text = text; }, resolve: {text: function($http) { return $http.get(/*your rest service that delivers the content*/) .then(function(result) { return result.data; }); } }, }) |
This would let you simply navigate between the states with your index’s buttons, while:
- ui-router injects the active state’s view in the <div ui-view></div> of the index.html,
- activates the state’s controller
- (eventually), pre-resolves data.
Obviously, being that each state’s template contains its own <div ui-view></div>, you can easily create some sub-states (i.e., tabs.list or content.submenu) and inject them, in turn, in the related parent’s state. This is wonderful! This is what ui-router, basically, has been designed for!
But, what if we would want/need to have a more complicated situation, with multiple views, injected within other views and, at the same time, parallel views one next to another? (click here to go to the mockup)
Multiple named views and abstract states
Multiple named views
The solution comes with ui-router itself, under the name of (multiple) named views. In practice, instead of just decorating a state with a single view (and a controller) we consider a state as, potentially, made of many views. Each view will be identified via its own name and will have its own controller (and data). The views will live within their state, being therefore activated when the navigation “wakes” the state (per /url, per $state.go or per ui-sref ).
This perspective is something like (1 state)-to-(n views). What ui-router enables is also (1 ui-view)-to-(n states). This means that you can control which view, from which state will be injected in which ui-view. This lets you injecting each view within each other, creating complex structures with substructures.
From theory to practice: all it’s based upon 3 elements:
- A name for the ui-view (you need it to identify where to inject your view)
1<div ui-view ='tabsView'></div> - The state in which the view that contains the ui-view is defined
- The definition of the NAME of each view (which controls everything)
12345678.state('tabs.first', {url: '/insideTab1',views: {"tabsView@tabs": {template: "<h5>First Tab's Menu Voice 1</h5>{{text}}"....}}})
The name of each view is made of: theNameOfTheTargetUiView@theNameOfTheState.ThatContainsThe.UiView.
In other words, you can use the name of the view to define where (in terms of state and ui-view) the view itself will be injected. You can think at this namespace as a way to define the routes of your application and to attach each of them to your SPA’s workflow.
Abstract states
Another element that I suggest you to use, when working with multiple ui-routes, is the definition of proper abstract states. These are states that you can’t directly access, but only navigate through. Those are perfect for data pre-resolution or for global element’s definition. An example of that can be the following definition of a set of bootstrap’s Tabs for our sample application:
- The view of the abstract state (/tabs.tpl.html):
1234567891011<h1>Tab Design 4 ui-router</h1><ul class="nav nav-tabs"><li ui-sref-active="active"><a ui-sref="tabs.first">Menu Tab</a></li><li ui-sref-active="active"><a ui-sref="tabs.second.content">Content Tab</a></li></ul><div ui-view ='tabs'></div> - And the related state’s definition:
1234567891011121314151617181920212223242526$stateProvider.state('tabs', {url: '/tabs',views: {"mainView": { //hence, you need to name "mainView" the ui-view div in the index.html//and make the proper changes also in the content viewabstract: true,templateUrl: "tabs.tpl.html",controller: function($scope, text) {//Controller in the abstract state is used only// to set "global" elements for the state and the sub-states$scope.title = "Tab Design 4 ui-router";$scope.text = text;},resolve: {text: function($http) {//RESOLVE IN THE ABSTRACT STATE ONLY AND ONLY ELEMENTS THAT ARE USEFUL// IN EACH SUB-STATE TO AVOID CONFUSION WITH THE SCOPESreturn $http.get(/*your rest service*/ ).then(function(result) {return result.data;});}}}}})
This way, each tab (say tabs.first and tabs.second) will have the skeleton of the abstract state as template and they will “live” within the parent’s ui-view=”tabs”.
Then, we need to define the content of each tab: for this purpose we use the multiple named view system of the previous paragraph! I will omit the rest of the code I want to share with you, because it would be too long for the article, but you can find it in the following Plunker.
Notice, in the app.js file how each state contains one or more named views and how each named view is injected in the ui-view declared in its name.
…another small hint
Please consider the code in the Plunker, if you navigate to the “Example Tabs” and then click on the “Content tab” you will see a “Replace Content” button. If you click it, you may notice that the content gets replaced with another and that you can undo this operation by clicking “Go Back”. This is reachable via ui-router and the multiple named views, simply by using the same ui-view with two different views of two distinct states. In this example, within the app.js, the “left_inside_secondTab@tabs.second” of “tabs.second.content” and the “left_inside_secondTab@tabs.second” of “tabs.second.content.replace”.
Hi.
Thanks for your article
I have a problem in multi tab state. When I change tab $scope of controller rest and initiate, how can prevent $scope state when change tab and state