We have already covered how routing works in AngularJS using ng-Route. In this article, we will go through another approach i.e using UI-Router. Before you step into UI-Router you may want to go through ng-Route to better understand this.
What is Angular UI-Router?
The UI-Router is a routing framework for AngularJS built by the AngularUI team. It provides a different approach than ngRoute in that it changes your application views based on the state of the application and not just the route URL. UI-Router is the best approach over ng-Route because it provides two most important features: nested views and multiple views. As with ng-Route, with this approach views and routes have not tied to the browser URL. You can change the view even if the browser URL does not change.
Suggested read:
Nested Views:
As the name suggests, Nested Views contain one view inside the other view like parent and child connection. To explain in a simple way, having a list of animals in one view. When you click on any animal you will get to see its features in another view.
To understand it well, take a look at the below image.
When the user selects an animal, the details are shown on the right side. The details are displayed when you select another animal.
This requirement can even be achieved with ng-Route by having a single view with two different controllers for each list and detail. But the result is not ideal as the view has both responsibilities of showing the list of animals and displaying/hiding its detail according to the selected animal. So it’s better to have a separate view and controller for each list and detail.
Multiple Views:
Having more than one view in common or on a single page is referred to as Multiple views. Usually, in any application we will have multiple parts associated with the single page, for example on the home page we display multiple different things of an application called a dashboard. To have each one of these parts we need to add a separate view for each. The following image explains it well.
Here, Users, Popular Posts, and Recent Posts are 3 different views displayed at the same time on a single page. Each will have a separate controller.
UI-Router Installation
To use angular UI-Router with version 1.2.x or 1.3.x you can follow the following steps.
- bower install:
$ bower install angular-ui-router OR
npm install:
$ npm install angular-ui-router
- Include ‘angular-ui-router.js’ and ‘angular-ui-router.min.js’ script tags in the index.html page after angular script tag.
- Include ‘ui.router’ dependency in main AngularJS main module.
var myApp = angular.module('myApp', ['ui.routrt']);
UI-Router as State Machine
UI-Router introduces a state machine design pattern. Unlike ng-Route, it does not work based on URL change rather it works on the state. Routes are referred to as states where URL becomes simply a property of it.
Look at below to understand how to configure routes using UI-Router.
//Routing using UI-router
var app = angular.module('demo', ['ui.router']);
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url:'/',
templateUrl: 'templates/home.html',
controller: 'HomeController'
})
.state('about', {
url:'/about',
templateUrl: 'templates/about.html',
controller: 'AboutController'
})
}]);
Let us understand the configurations.
- The angularJS service $routeProvider with ng-Route becomes $stateProvider when using UI-Router.
- $urlRouterProvider is used for two main reasons.
- To establish default route when URL does not meet with any route or does not have specific routes.
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/'); }])
- To allow developers to listen for a window location change and redirect to a route that has a state defined.
app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { $urlRouterProvider .when('/legacy-route', { redirectTo: '/' });}]);
In summary, $urlRouterProvider lets you handle the requests when state design pattern does not make any sense.
How to navigate to a page?
- In a controller, to navigate to a page we use the $state as below.
$scope.redirectToAbout = function(){ $state.go(‘about’); }
- In an HTML page, we use ui-sref where ui is an AngularUI project’s directive is used as prefix and sref is stated reference.
<a ui-sref="home">Home</a>
Example for AngularJS Routing using UI-Router
Let’s understand UI-Router with a sample application. It explains both Nested Views and Multiple views.
The application has the following structure.
The index.html page looks as given below.
<!DOCTYPE html>
<html>
<head>
<!-- CSS (load bootstrap) -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<style>
.navbar { border-radius:0; }
</style>
<!-- JS (load angular, ui-router, and our custom js file) -->
<script src="http://code.angularjs.org/1.2.13/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.8/angular-ui-router.min.js"></script>
<script src="app.js"></script>
</head>
<!-- apply our angular app to our site -->
<body ng-app="routerApp">
<!-- NAVIGATION -->
<nav class="navbar navbar-inverse" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" ui-sref="#">AngularUI Router</a>
</div>
<ul class="nav navbar-nav">
<li><a ui-sref="home">Home</a></li>
<li><a ui-sref="about">About</a></li>
</ul>
</nav>
<!-- MAIN CONTENT -->
<!-- THIS IS WHERE WE WILL INJECT OUR CONTENT ====== -->
<div class="container">
<div ui-view></div>
</div>
</body>
</html>
Here, we are using bootstrap for styling and also we are loading Angular UI Router script after the angular script.
In this page, we have two simple links called Home and About. We use ui-View as ng-view in ng-Route to load a particular view as the user clicks on any link.
<div ui-view></div>
Now we will have all our routes configurations in the config file i.e. app.js
Here we have configured routes for the home and about page. Routes are represented as states as explained above.
Controllers are defined in the route definition itself as it has less content.
app.js
var routerApp = angular.module('routerApp', ['ui.router']);
routerApp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
// HOME STATES AND NESTED VIEWS ========================================
.state('home', {
url: '/home',
templateUrl: 'partial-home.html',
controller: function($scope) {
$scope.animals = [{id:1,name:'Dog'},
{id:2,name:'Cat'}];
}
})
// nested list with custom controller
.state('home.list', {
url: '/list/:id',
templateUrl: 'partial-home-list.html',
controller: function($scope,$stateParams) {
$scope.num = $stateParams.id;
}
})
// ABOUT PAGE AND MULTIPLE NAMED VIEWS =========
.state('about', {
url: '/about',
views: {
'': { templateUrl: 'partial-about.html' },
'columnOne@about': { template: '<strong>Hey I am a column!</strong></br><p style="color:red">I represnt one view</p>' },
'columnTwo@about': {
templateUrl: 'table-data.html',
controller: function($scope){
$scope.animals = [
{id:1,name:'Dog'},
{id:2,name:'Cat'}
];
}
}
}});
});
Nested Views with UI-Router
The Home page introduces Nested Views.
We have two views: partial-home.html(main) and partial-home-list.html(child). The main view displays the list of animals. As the user clicks on any animal, the child view is shown which displays the animal description.
So let’s have partial-home.html(main) and partial-home-list.html(child) views.
partial-home.html
<div>
<h1>Home Page</h1>
<p>This page demonstrates <span class="text-danger">nested</span> views.</p>
<ul>
<li ng-repeat="an in animals">
<a ui-sref="home.list({id:an.id})">{{an.name}}</a>
</li>
</ul>
</div>
<div ui-view></div>
partial-home-list.html
<div ng-if="num == 1"><strong>Dog</strong> is clicked</div> <div ng-if="num == 2"><strong>Cat</strong> is clicked</div>
The output of an above-nested view is shown below.
Multiple Views with UI-Router
The About page introduces Multiple views.
Here, we have main page “partial-about.html” and two views defined in route definition in app.js file I.e ‘columnOne@about’ & ‘columnTwo@about’.
Both views are displayed at once as the main page is loaded.
Let’s have the main page as shown below:
partial-about.html
<div>
<h1>About Page</h1>
<p>This page demonstrates <span class="text-danger">multiple</span> and <span class="text-danger">named</span> views.</p>
</div>
<div class="row">
<div class="col-sm-6">
<div ui-view="columnOne"></div>
</div>
<div class="col-sm-6">
<div ui-view="columnTwo"></div>
</div>
</div>
- For ‘columnOne@about’ template, please refer app.js file(about state).
- For ‘columnTwo@about’, which point to the file shown below.
table-data.html
<h2>Animals</h2>
<p style="color:red">I represent another view</p>
<table class="table table-hover table-striped table-bordered">
<thead>
<tr>
<td>Id</td>
<td>Name</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="an in animals">
<td>{{ an.id }}</td>
<td>{{ an.name }}</td>
</tr>
</tbody>
</table>
The output of Multiple views is shown below.
Note: You can view the application output in http://run.plnkr.co/preview/cj98vsfy900093c5w9f0mpkir/#/home
How to define State Name?
Understanding the state name as shown above I.e columnOne@above is very difficult. So this syntax of state name can be understood as below.
- The name before “@” is the name of the view that we are going to replace with the template(where ui-view is defined) when this route is requested. So this name becomes the value of the ui-view directive.
Ex. <div ui-view=”columnOne”></div>
- The name after “@” indicates that where the ui-view with the value view name can be found. The location is represented as a state which contains URL instead of defining URL directly as with ng-Route.
So view name + “@” + state name become state name to point to view.
Conclusion:
This approach is the best and most robust as the application is seen in states rather than URLs. You can use UI-Router instead of ng-Route to make your application more robust and reusable.