UI-Router: Why many developers don’t use AngularJS’s built-in router

AngularJS is an amazing JavaScript framework for building rich client-side applications but the truth is many developers don’t use the built-in router. They instead use the AngularUI project’s UI-Router because it has two important features: multiple views and nested views. This article explains these features and why they are important and shows you a real-world example of using these features together.

Why You Should Use UI-Router

Multiple Views

Most applications can be broken up into regions. At a minimum, applications usually have a header, a main content area, and a footer.

Commonly, applications may have an additional sidebar on the left or right side of the page as shown below.

multiple-views-sketch

In most use cases, all of these regions (views) are shown on the page at the same time. With the built-in AngularJS router, ngRoute, only one view (ng-view) is allowed per page. This limitation causes people to use includes (ng-include) or other workarounds to create a layout or master page for their application. UI-Router supports multiple views and each can have it’s own corresponding Controller so that each of these regions can be encapsulated and reused throughout the application if needed.

Nested Views

The common example of a nested view in applications is a master/detail or, more specifically, a list/detail page. Many applications show a list of items then when you click on an item you see the detail for that item. Taking this example further, you might then click an edit link when viewing the item’s details that takes you to an editable form for the item (see the diagram below to visualize).

master-detail-sketch

This scenario is easily achieved with the built-in AngularJS router, ngRoute, if the list and detail are on separate pages (or views as they are called in AngularJS). However, if you want the list to remain on the page while you show the detail to the right or below the list this becomes more challenging. To be clear, this requirement can be achieved with ngRoute by sharing a single view with two controllers: one for the list and one for the detail and hiding and showing the detail as needed. The result is not ideal because we would like the list and detail to each have their own controller and view with only one responsibility (showing a list or showing item details). By encapsulating these user interface areas in their own view we can have a more composable UI that allows us to bring the pieces together or break them apart as needed to meet requirements. Nested views enable us to not only bring these views together at the same time but also to nest a view inside another view as is done in the list/detail example.

History

When AngularJS was first released ngRoute had a similar feature set as other routers at the time, such as the router included in the Backbone.js library as well as the stand-alone routing libraries History.js and Sammy.js. In summary, they mapped a route or URL to JavaScript code that needed to be run when the URL changes and added entries to the browsers history appropriately so that the back button didn’t break.

Eventually competing JavaScript MV* frameworks like Ember.js and Durandal.js innovated and came out with more robust routers that supported multiple views and nested views and implemented the state machine design pattern internally.

AngularJS responded to this by removing ngRoute out of the core angular.js download in version 1.1.6 (most people just say version 1.2). It is still available for download from the AngularJS site but is no longer in the core.

The AngularJS community responded and the most popular library that emerged was the AngularUI project’s UI-Router.

The AngularJS team which for several months (as a consultant) included Rob Eisenberg, the Durandal.js and Aurelia (nextgen Durandal) creator, has been working on a rewrite of the Router for the future version of AngularJS 2.0 and have stated it will eventually be ported back into a point release of AngularJS version 1.3 which was just recently released.

If you want more details about the history and the pros and cons of various routers the public design document for the AngularJS 2.0 router is available here Note that you’ll need to click the green button labeled “suggesting” and choose “viewing” as shown below to make it legible.

angularjs-router-design-document-1

angularjs-router-design-document-2

Install

To use the UI-Router with version 1.2.x or 1.3.x of AngularJS you can do one of the following to get the javascript source code:

download

download the release or minified versions

bower install

$ bower install angular-ui-router

npm install

 $ npm install angular-ui-router

Include script tag

Include angular-ui-router.js or angular-ui-router.min.js in your index.html, after the Angular script tag (see below)

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<script src="js/angular-ui-router.min.js"></script>

Include Dependency

Include the ‘ui.router’ dependency in your main AngularJS module.

var myApp = angular.module('myApp', ['ui.router']);

Notice the module name is ui.router not ui-router (using the hyphen is a common mistake).

Router as State Machine

UI-Router introduces a state machine design pattern abstraction on top of a traditional router. Routes are referred to as states and the URL becomes simply a property of the state.

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'
        })
 
}]);

When you create links you can simply refer to the state name inside a ui-sref directive instead of using the URL.

So this:

 <a ui-sref="home">Home</a>

Renders:

 <a href="#/">Home</a>

In the example above ui-sref can be understood as follows: ui is the directive prefix for all AngularUI project directives and sref is a play on the traditional href of an HTML anchor tag and stands for state ref.

In the Controller

Here is an example of how to do a redirect in a Controller to a state.

$scope.redirectToAbout = function(){
    $state.go('about'); 
}

$routeProvider becomes $stateProvider

The AngularJS service injected to provide routing which is $routeProvider with ngRoute becomes $stateProvider when using the UI-Router.

$urlRouterProvider

The $urlRouterProvider is there for two main purposes. To establish a default route for URLs that don’t have a specific route.

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 cases where the state machine abstraction of the $stateProvider doesn’t make sense.

So now that you’ve got the basics of the UI-Router lets get back to those features that make it better than ngRoute.

UI-Router in Action

We will look at an example of nested views first then we’ll look an example of multiple views. After we get our heads around each we’ll put the two features together to show how they can be used in a real-world application.

Nested Views with UI-Router

Here is a list/detail example of nested views using UI-Router. The example displays a list of TV shows.

angularjs-ui-router-nested-views-1

If you click on a row you’ll see a detailed description for the selected show.

angularjs-ui-router-nested-views-2

Application Shell (index.html)

AngularJS applications are single-page application where views are inserted into a shell page. Here is our shell page index.html.

<!doctype html>
<html id="data-ng-app" data-ng-app="demo">
    <head>
        <meta charset="utf-8">
        <title>ui router demo</title>
        <style type="text/css">
            .selected{background-color: #efefef; width:120px; } 
            .detail{width: 300px;margin: 30px;border-top: 1px solid #efefef;}
        </style>
        <!-- IE8-HTML5: https://code.google.com/p/html5shiv/ -->
        <script src="js/libs/html5shiv.js"></script>
 
    </head>
    <body id="index">
 
        <!-- Angular UI Router Directive for template insertion -->
        <div id="content" ui-view></div>
 
        <script src="js/libs/angular.js"></script>
        <script src="js/libs/underscore.js"></script>
        <script src="js/libs/angular-ui-router.js"></script>        
        <script src="js/main.js"></script>      
    </body>
</html>

The <div id="content" ui-view></div> will have the first level or parent view (in our example shows.html) placed inside of it by the UI-Router.

Home page view (templates/shows.html)

The list view is shows.html.

<ul>
    <li ui-sref-active="selected" ng-repeat="show in shows">
        <a ui-sref="shows.detail({id: show.id})">{{show.name}}</a>
    </li>
</ul>
 
<div class="detail" ui-view></div>

As mentioned before the index.html page has a ui-view attribute directive into which this view (shows.html) is rendered when the corresponding route is requested.

Notice how there is another ui-view nested inside this shows.html view. This ui-view is where a child view of the parent ‘shows’ view is rendered. In this example shows-detail.html is the child view.

Shows detail view (templates/shows-detail.html)

The detail view is shows-detail.html

<h3>{{selectedShow.name}}</h3>
<p>
    {{selectedShow.description}}
</p>
</code>

Controllers

And there is a corresponding controller for each view.

ShowsController

The ShowsController loads an in-memory array of shows from the ShowsService.

app.controller('ShowsController', ['$scope','ShowsService', function($scope, ShowsService) {
    $scope.shows = ShowsService.list();
 }]);

ShowsDetailController

The ShowsDetailController looks up the show by id using the ShowsService and sets it as the selectedShow on the $scope.

app.controller('ShowsDetailController', ['$scope','$stateParams', 'ShowsService', function($scope, $stateParams, ShowsService) {
        $scope.selectedShow = ShowsService.find($stateParams.id);
 }]);

Configuration

We need to configure the UI-Router using the $stateProvider.

When we define a state as 'parentstatename.childstatename' the convention of simply adding the period when defining the state name tells UI-Router that the child state is nested under the parent state.

app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/shows');
 
    $stateProvider
        .state('shows', {
            url:'/shows',
            templateUrl: 'templates/shows.html',
            controller: 'ShowsController'
        })
        .state('shows.detail', {
            url: '/detail/:id',
            templateUrl: 'templates/shows-detail.html',
            controller: 'ShowsDetailController'
        });
}]);

What is really great about nested views is that the list controller just has implementation details regarding the list and the detail controller is only concerned with showing details.

To show how decoupled these are we only need to change the routing configuration to not nest the details and we have two separate virtual pages (one for list and one for details). More specifically, we’ll change the state name 'shows.detail' to 'detail'.

$stateProvider
    .state('shows', {
        url:'/shows',
        templateUrl: 'templates/shows.html',
        controller: 'ShowsController'
    })
    .state('detail', {
        url: '/detail/:id',
        templateUrl: 'templates/shows-detail.html',
        controller: 'ShowsDetailController'
    });
    ...

And change the link to the state from <a ui-sref="shows.detail({id: show.id})">{{show.name}}</a> to <a ui-sref="detail({id: show.id})">{{show.name}}</a>

Now our example will display the views separately as if they are two separate pages.

Service

The ShowsService is our data access layer in this example and just keeps an in-memory array and uses underscore.js to easily work with the collection.

app.factory('ShowsService',function(){
    var shows = [{
        id: 1,
        name: 'Walking Dead',
        description: 'The Walking Dead is an American post-apocalyptic horror drama television series developed by Frank Darabont. It is based on the comic book series of the same name by Robert Kirkman, Tony Moore, and Charlie Adlard. It stars Andrew Lincoln as sheriff\'s deputy Rick Grimes, who awakens from a coma to find a post-apocalyptic world dominated by flesh-eating zombies.'
    },
    {
        id: 2,
        name: 'Breaking Bad',
        description: 'Breaking Bad is an American crime drama television series created and produced by Vince Gilligan. The show originally aired on the AMC network for five seasons, from January 20, 2008 to September 29, 2013. The main character is Walter White (Bryan Cranston), a struggling high school chemistry teacher who is diagnosed with inoperable lung cancer at the beginning of the series.'   
    },
    {
        id: 3,
        name: '7D', 
        description: 'The 7D is an American animated television series produced by Disney Television Animation, and broadcast on Disney XD starting in July 7, 2014. It is a re-imagining of the titular characters from the 1937 film Snow White and the Seven Dwarfs by Walt Disney Productions'
    }];
 
 
    return {
        list: function(){
            return shows;
        },
        find: function(id){
            return _.find(shows, function(show){return show.id == id});
        }
    }
 });

Multiple Views with UI-Router

Below is an example of several regions on a page including a header, content, and footer being managed on a page with UI-Router using multiple views.

angularjs-ui-router-multiple-views-1

There is some primary navigation and various content is filled into the regions depending on the virtual page the user navigates to in the application.

angularjs-ui-router-multiple-views-2

Application Shell (index.html)

<!DOCTYPE html>
 <html class="no-js">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Index</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
 
 
    </head>
    <body ng-app="demo">
 
       <div ui-view="header"></div>
       <div ui-view="content"></div>
       <div ui-view="footer"></div>
 
    <script src="/js/bower_components/angular/angular.js"></script>
    <script src="/js/bower_components/angular-ui-router/release/angular-ui-router.js"></script>
    <script src="/js/main.js"></script>
 
    </body>
</html>

Notice the ui-view attribute directives have names assigned to them: header, content, and footer. These names are referenced when we configure the router to say which view/template and controller to use for each region of the page.

Templates/Views

The templates are straight-forward to keep the example simple. Header.html has some navigation that uses the ui-sref directive to navigate to various application states/routes.

partials/header.html

<div class="ul">
    <li><a ng-href="/">Home</a></li>    
    <li ui-sref-active="active"><a ui-sref="dashboard">Dashboard</a></li>
    <li ui-sref-active="active"><a ui-sref="campaigns">Campaigns</a></li>
</div>

partials/content.html

<p>This is the default content.</p>

partials/footer.html

<p>This is the footer.</p>

partials/dashboard.html

<h2>Dashboard</h2>

partials/campaigns.html

<h2>Campaigns</h2>

The dashboard and campaigns view templates are used to replace the default content in content.html when the corresponding routes are requested.

Configuration

As in the previous example we use the $stateProvider to configure the states (routes).

The key takeaway below is to notice that instead of having one templateUrl and controller per URL you instead have a collection of views each with its own templateUrl and controller.

So this:

.state('home',{
        url: '/',
        templateUrl: '/templates/partials/header.html',
        controller: 'HomeController'
    })

Becomes:

.state('home',{
        url: '/',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html',
                controller: 'HeaderController'
            },
            'content': {
                templateUrl: '/templates/partials/content.html',
                controller: 'ContentController' 
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html',
                controller: 'FooterController'
            }
        }
    })

Here is the full code for the example (note: controllers are not needed for the example I just put them in above for completeness).

var app = angular.module('demo', ['ui.router']);
 
app.config(function($stateProvider, $urlRouterProvider){
 
    $urlRouterProvider.otherwise('/');
 
    $stateProvider
    .state('home',{
        url: '/',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html'
            },
            'content': {
                templateUrl: '/templates/partials/content.html' 
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html'
            }
        }
    })
 
    .state('dashboard', {
        url: '/dashboard',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html'
            },
            'content': {
                templateUrl: 'templates/dashboard.html',
                controller: 'DashboardController'
            }
        }
 
    })
 
    .state('campaigns', {
        url: '/campaigns',
        views: {
            'content': {
                templateUrl: 'templates/campaigns.html',
                controller: 'CampaignController'
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html'
            }
        }
 
    })
});

The other thing to notice is that if I don’t fill in a region with a view then it will not be shown when the user navigates to that route. This is not ideal and makes us repeat ourselves quite a bit so in the next section we will look at how to remove this repetition by using the nested views we saw earlier.

Multiple Views and Nested View with UI-Router

Now that we understand each of these powerful features lets bring them together to give you a better idea of how you might want to set-up a real-world application.

Configuration

We’ll start with the configuration since the view templates are the same as in the multiple views example.

var app = angular.module('demo', ['ui.router']);
 
app.config(function($stateProvider, $urlRouterProvider){
 
    $urlRouterProvider.otherwise('/');
 
    $stateProvider
    .state('app',{
        url: '/',
        views: {
            'header': {
                templateUrl: '/templates/partials/header.html'
            },
            'content': {
                templateUrl: '/templates/partials/content.html' 
            },
            'footer': {
                templateUrl: '/templates/partials/footer.html'
            }
        }
    })
 
    .state('app.dashboard', {
        url: 'dashboard',
        views: {
            'content@': {
                templateUrl: 'templates/dashboard.html',
                controller: 'DashboardController'
            }
        }
 
    })
 
    .state('app.campaigns', {
        url: 'campaigns',
        views: {
            'content@': {
                templateUrl: 'templates/campaigns.html',
                controller: 'CampaignController'
            }
        }
 
    })
 
    .state('app.subscribers', {
        url: 'subscribers',
        views: {
            'content@': {
                templateUrl: 'templates/subscribers.html',
                controller: 'SubscriberController'      
            }
        }
 
    })
    .state('app.subscribers.detail', {
        url: '/:id',
        /*
        templateUrl: 'templates/partials/subscriber-detail.html',
        controller: 'SubscriberDetailController'
        */
 
        views: {
            'detail@app.subscribers': {
                templateUrl: 'templates/partials/subscriber-detail.html',
                controller: 'SubscriberDetailController'        
            }
        }
 
    });
 
});

We create a default state (route) at / named app. In this app state we can define the default content for our header and footer regions. Then if we make every other page in the application a nested view under the app by using the dot syntax for example app.campaigns. Notice we only need to replace the content region (ui-view='content') unless we need to change the header or footer because these views are nested under the default app state.

State names

The most difficult concept to grasp in the code above is the state
name syntax with the @ in the middle. The syntax for the state name can be explained as follows:

Two questions need to be answered when writing a state-name:

  1. What is the name of the view (region) I want to replace with my template when this route is requested: view-name? More specifically, this is the value of the ui-view attribute directives. Here are some examples of ui-view directives and their corresponding view-name:
    • ui-view='content' = content
    • ui-view='header' = header
    • ui-view='footer' = footer
  2. Where can I find the ui-view with that view-name?
    • this location is not expressed as a templateUrl but instead as the state that contains that template
    • when the template ui-view with the view-name is in the application shell template (index.html) because index.html is not defined in any state you should leave name the state as empty string (”) or nothing

Putting this together, the syntax is question1@question2 or more specifically view-name@state-name.

So if you need to find the content view-name in index.html it would be:
'content@'

  • See the second bullet under #2 about leaving the state blank when the view-name is in the shell page (index.html).

If you need to find the content view-name in subscribers.html it would be:
'detail@app.subscribers'

Application Shell (index.html)

The shell page doesn’t change from the previous example and simply defines named views for each region of the page: header, footer, and content.

Views/Templates

Header (partials/header.html)

The header has updated ui-sref references to the nested states for example .campaigns with the period not campaigns. Note the parent state is inferred when we say .campaigns.

<div class="ul">
    <li><a ng-href="/">Home</a></li>    
    <li ui-sref-active="active"><a ui-sref=".dashboard">Dashboard</a></li>
    <li ui-sref-active="active"><a ui-sref=".campaigns">Campaigns</a></li>
    <li ui-sref-active="active"><a ui-sref=".subscribers">Subscribers</a></li>
</div>

Below are the other new subscriber templates in the example.

partials/subscribers.html

<h2>Subscribers</h2>
 
<ul>
    <li ng-repeat="subscriber in subscribers">
 
        <a ui-sref=".detail({id: subscriber.id})" > {{subscriber.name}}</a>
        {{subscriber.email}}
    </li>
</ul>
 
<div ui-view="detail"></div>

partials/subscriber-detail.html

{{selected.description}}

Conclusion

The syntax for the state names is difficult to grasp but the benefits of having a robust router which allows you to compose your user interface from well encapsulated view/controller pairs is worth it in my mind. So please feel free to take the last example as a starting point for the shell of your application and run with it to build an amazing and much more maintainable application. Let me know if you are or are not already using the UI-Router and what other questions you have about it.

Code

The code examples referenced in this post are available here on Github.
https://github.com/craigmckeachie/ui-router-examples

68 Responses to “UI-Router: Why many developers don’t use AngularJS’s built-in router”

  1. Bill Gerold December 12, 2014 at 7:48 am #

    Hi Craig,
    Great Article – I’ve been using ui-router since I started with Angular. I was very hard to get my head around the nested/child views.
    The article makes it very clear – I wish I had access to it when I was starting out.

    Keep Up the Good Work

    Thanks again,

    Bill Gerold

    • Jose Carlos June 7, 2015 at 6:58 pm #

      I dont think it can be good at real huge projects, I used to work at some projects with several ( more than 100 pages and different functionalities) I cant imagine how to mantain such a mess!
      I think it could be very easier just break down with several pages independent with just pure links for location. If you are creating some kind of wizard yes, could be interesting using such approach.

  2. Dean Peterson January 7, 2015 at 12:43 pm #

    FYI, Rob Eisenberg has left the AngularJS team last November (http://eisenbergeffect.bluespire.com/leaving-angular/)

    • Craig McKeachie January 7, 2015 at 1:02 pm #

      Thanks for the comment…I had heard this but hadn’t updated the post.

  3. Kevin McGee January 9, 2015 at 9:20 pm #

    Watch for @theEisenbergEffect on an upcoming episode of The ChangeLog podcast. After he announces his new project, Aurelia, consider having him on your cast, maybe…

  4. Erhard Karger January 29, 2015 at 10:40 am #

    It is a great article. All the pros are mentioned. But what I do not understand what is the practical advantage over a directive ?
    What I mean is : Is it really neccessary to hold the Roy Fieldings paradigm in the deeps. Does the combination with ngRoute , $location and directives cover all. To gain the same ? Or even more because a url is a list and not a tree. And a single page app is more like a tree.
    I see it a little bit in that way : ngRoute is a menu switcher into the particular page the page (view) is combined of visual modules which are directives. Is it necessery to see the url as a ressource to navigate the Ressources or is the url a Ressource to navigate thru parts of programms(which is another word for the singlepage app).
    I made applications with directives and ngRoute and I do not see (perhaps I do not understand the advantage) why to switch to ui-Router. And if the UI-Router pattern is so good why do I need directives ? I there a pattern out there when to use what (I have not found one) ? I switched from ember to angular, because of the modularity of directives. So can you help me to explain a little bit a pattern when to use what ?

    Thanks a lot

    Erhard

    • Craig McKeachie January 30, 2015 at 11:53 am #

      This is a very good question that deserves an entire post which I may do in the future but a couple thoughts off my head. Directives in AngularJS have a horrible API currently because they are trying to do too many things: be web components, be structural components (flow of control in the view: ng-if, ng-repeat) and decorators (ng-class) and the Angular team is fixing this in 2.0. That said if I just need a reusable section of a page (web component but only useful in my app) then Angular directives can be too complex and difficult to maintain and debug compared to a small controller in conjunction with a more powerful router. But even if directives were easier to write and maintain there are different levels of reuse required for different web components for example a datagrid or a datepicker is clearly useful across apps and is worth complexity to get that much reuse. I’m not sure this is the case for lots of things people use directives to do. Another consideration is that a directive doesn’t have a route or a url associated with it so if you want that component to have states that can be url bookmarked and sent in emails etc… you need urls so your back button works throughout those states.

      • K May 27, 2015 at 2:52 pm #

        I have a different perspective. I’m a big fan of opinionated frameworks, I think they result in better systems due to uniformity of implementation and predictability. Angular isn’t really opinionated; outside of providing its own IoC container it doesn’t really truly demand you do anything one particular way. Even scopes are a malleable, modifiable construct and they form the core of the communicative interpolation of the application.

        Most people I’ve seen who want to use UI router do so because they never actually learned what directives are or how scope management actually works, but still wanted to have the ability to late bind controllers with templates (even though they failed to understand how the controller provider system works in general; register a controller from a remotely loaded script file to enable a more extensible resolver? shenanigans, and impossibility from their perspective). They also have no idea how powerful behaviors can be, and completely missed the boat on the good parts of aspect oriented programming. They’re servlet/asp.net programmers who never got more than skin deep in concepts that pervade our industry and as such don’t know the good bits from the bad ones.

        Is there additional complexity in authoring a directive?

        From one perspective, yes; the authoring of a directive has more components that can be materialized. Controllers simply work, as if by magic. Their functionality binds effortless the data constructs and dom elements that comprise them.

        From another perspective, not really; entirely controller-derived ui heavily obfuscates what its configuration is, so your options are digging and inference. A properly written directive lays bear its intent, and the full scope of its constraints and configuration plainly.

        The last point I’ll make is that “states,” whether as in application, as in user or as in general are out-dated, near antique. The whole world has been moving to a stateless paradigm for years, where the sum of the process combined with the operational vectors exposed by our apis become more than the sum of their parts. From that perspective, ui router appears almost curmudgeonly, a bad code smell at best, at worst its core principles amount to “stateName: {//do something} … … goto stateName.”

        • Rick October 11, 2017 at 1:11 pm #

          It’s late 2017 now and components have been introduced to Angular 1.x for awhile now and so I think the only valid reason for ui-router now is if you want people to be able to navigate directly to a deep nested page. So it would seem if you have more of a traditional website vs an SPA it’s a no brainer as you’d expect to be able to send links around. An SPA generally isn’t designed as such so using components and ngRoute is probably best.

          This is an interesting issue in web design though. Pretty much anything can be an SPA these days and doesn’t require sending links around to specific pages. If we think about how we’re basically turning web pages into applications and when you think of traditional desktop applications there was never any concept of going directly to some kind of deeply nested area in the application from a click of a button like there COULD be in a web page, we see these web apps can be treated as a marriage between desktop apps and web pages if we use ui-router but is the complexity worth it? I guess each app would need to examine that. Google sheets probably doesn’t make sense to use ui-router but imdb? Probably given you would want to send a link right to a specific page of information to a friend.

          I think the question we could ask though is are routes really the best way to do this in a heavy javascript web app? I mean we had no other options in the past. Url’s is how you told the technology at the time how to get to somewhere, but since our javascript frameworks are intercepting these and running it’s own code to find where it needs to be are routes the best way? I don’t know. In a desktop app if you wanted a similar idea would routes be how you’d do it? That would probably tell us since that’s what we have today with angular and other client side apps.

  5. Bradley Bossard March 1, 2015 at 11:01 pm #

    Is there a github repo or somewhere to download the code related to this article? I kind of wanted to see how the how thing works together. Thanks in advance.

  6. reddy Vijay March 5, 2015 at 1:38 am #

    great article but my is..

    why we cant use controllerUrl in ui.router.i am using requirejs and there he use ngRouter instead of ui.router and i was trying to use ui.router its working. but if i use separate file to the controller and use controllerUrl in $stateProvider that time its not working…so can u help to solve this….

    Thanking You..

    • Craig McKeachie March 5, 2015 at 10:17 am #

      I’m not familiar with ControllerUrl in ui.router…do you mean templateUrl? If you are really stuck I would post a plunk on stackoverflow because it’s difficult to debug without more context (code). Feel free to add a comment here with a link to the stackoverflow question and I’ll take a look.

  7. elitemunky March 6, 2015 at 11:44 am #

    Extremely well written Sir. Your time and effort on this article is greatly appreciated.

    • Craig McKeachie March 6, 2015 at 12:01 pm #

      Thanks for kind words.

      • elitemunky March 6, 2015 at 12:50 pm #

        Absolutely . You articulated these concepts extremely well. My only concern is if we want the user to be able to land on a nested view dependent on the parent(s).

        For instance, example.com/subscriber/33/subscription/2/details
        Preceded by navigatable lists of subscribers and subscriptions, all accessible by direct url. Im now attempting a plunker of this ill post here but if you see any intrinsic pittfalls here please deter me.

        • elitemunky March 6, 2015 at 1:06 pm #

          My bad.

          example.com/subscribers/33/2/details would fit your model and suit my needs perfectly! thx again

  8. Rajesh March 7, 2015 at 8:03 pm #

    Hi, Nice article… quick question.

    I want to use different views as part of the same HTML . This is needed when different services (services of SOA fame, not angular services) are providing different aspects of data all displayed as part of the same table.

    How will the data of different views be correlated and presented in this case?

    Warm Regards
    Rajesh

  9. Rajesh March 7, 2015 at 8:13 pm #

    Hmm… i meant i want to use it as part of the same HTML TABLE…

    Warm Regards
    Rajesh

    • Craig McKeachie March 10, 2015 at 10:08 am #

      I believe the AngularJS feature you are looking for is Angular services. For example, if you want to create an AngularJS service for say Orders that has Orders as well as the customer data associated with the order you would create a service object (think business object or data access object) to load that data via $http or $resource either using one call or separate depending on how your backend is setup. This Angular service object can hold multiple arrays of data or hierarchies of data and make it available in your controller and scope which then allows it to be used in the html table in your view.

  10. Dan Lokman March 13, 2015 at 7:15 pm #

    Here’s a good resource for AngularJs.

  11. Jay March 16, 2015 at 4:00 pm #

    Hi Craig,
    from the templates/campaigns.html , I want to move to Dashboard.html , how can I achieve this ?

  12. Andrea D. March 25, 2015 at 2:45 pm #

    Great post bro! (y)

  13. looneytunes March 27, 2015 at 6:05 pm #

    Really nice article on angular ui-router. Though i have had some questions which is related to when a single state has multiple views, why does each view need to have its own controller. I tried to have one single controller specified in the state. butit does not work. How can i make sure i can use a single controller for all my views in that state?

    • Craig McKeachie March 30, 2015 at 9:46 pm #

      In general, each view has its own controller because you want your controller code to be slim and have a single-responsibility. If you really want one view and don’t care that the back button works as you transition through the views you can simply hide and show parts of the view using ng-class or ng-show (so you can have one view). There are cases where this is fine I just feel like the majority of the time I’d rather have the code for the list separate from the the code for the detail and choose when to bring them together (compose them) on the page and when you’d rather leave them separate.

  14. Chip Edwards March 31, 2015 at 3:13 pm #

    Nicely done.

  15. Mike Collins April 9, 2015 at 3:42 pm #

    If your templateurl is a dynamic page like asp, php cfm how do I tell ui router to get a new version each time user enters. I may not need this all the time, but it would be nice to know it supports it.

    • Craig McKeachie April 9, 2015 at 10:04 pm #

      If something in the page needs to be dynamic, the dynamic data should be interspersed on the client in the browser with the data coming from a json web api and being brought together on the client in the web browser via JavaScript. Under the hood these libraries use the $templateCache Angular service so it is possible to invalidate the cache for that chunk of html as you are asking but generally I’d try hard to keep the templates static and make them dynamic in JavaScript in the browser (embrace the single-page application architecture don’t mix architectures).

  16. Warlette April 15, 2015 at 7:36 am #

    This is fun. Nice.

  17. Al April 16, 2015 at 2:49 pm #

    Great article thanks! Very clear and easy to follow.

    I found myself looking for links to examples / demos in the tutorial so I could see your code working. I took the time to sit down and put some code together based on your examples, which people can look at one GitHub if they are interested to try things out.

    Demo: https://f1lt3r.github.io/angular-ui-router-examples/multi-nested/
    Repo: https://github.com/f1lt3r/angular-ui-router-examples/

    • Craig McKeachie April 16, 2015 at 3:09 pm #

      Thanks. I need to get my code out of my private repo and out there but much appreciated.

    • Giri October 31, 2015 at 3:17 am #

      Thanks AI. I could now easily understand the tutorial.

  18. jesus April 29, 2015 at 3:01 pm #

    Great tutorial, I had to read it carefully to understand all the concept, Tks

  19. Manoj May 4, 2015 at 6:11 am #

    Excellent presentation and thanks for covering the topic on ui-router in detail

  20. Prashanth May 4, 2015 at 7:30 pm #

    Thank you for posting this article. Very well written with neat explanation.

  21. Madrus May 23, 2015 at 1:19 pm #

    Thanks Craig. I have finally got how ‘@’ notation works with nested multiple views. Extremely powerful explanation.

    There is one problem I still cannot get my head around. I need my website to be multilingual. I can’t figure out how to add prefixes to paths instead of suffixes. I mean like ‘/en/home’ and ‘/nl/home’ depending on the language the user chooses by clicking the corresponding flag in the header. I can do everything under ‘home’ like in your article and I use the translator service to provide me with the right language strings. But how do I add language prefix before the root directory so that I can reuse same views and just populate them with a different language?

    Or maybe you can suggest a better or simpler approach?

    • Craig McKeachie May 27, 2015 at 1:06 pm #

      What I’ve seen suggested in some of the AngularJS books because it is difficult to impossible to dynamically add routes is to have multiple home pages. So you would create a directory structure for each language you support and then put the default or index or home page for that language under the directory and go from there. Note: this doesn’t preclude you from reusing view templates across directories if you use something like ng-translate to localize. I haven’t had this specific requirement so you might want to go to the angularjs google group to get a few more ideas.

  22. Rama Akella May 24, 2015 at 9:25 am #

    Hi Craig,

    This article cleared my understanding regarding the multi/nested views. But i had a doubt regarding this “@” in the view-name attribute ie detail@app.subscribers. Is it required only when we have the same view names in parent and child?

    Thanks,
    Rama Akella

    • Craig McKeachie May 27, 2015 at 1:00 pm #

      The @ is required when you nest views which might be the same thing you are saying because to nest a view you prefix it with the parents name and then dot

  23. camden_kid June 11, 2015 at 9:19 am #

    Really well written and easy to follow. Thank you.

  24. Kay June 23, 2015 at 4:32 pm #

    UI router is directives for dummies. Since they created it, they’re gone to extraordinary lengths to ensure that values like attr, element, etc are injected into the controller (which, btw violates the angular near-prime directive to not modify the dom from the controller). It’s just their half-baked workaround for dealing with all the conditions where you may need to do things like interact with the dom in a contextual manner and not revert back to jquery spaghetti code (integrating third party libraries, dealing with legacy code not in angular, working with objects outside the state hierarchy, etc.) It’s also not possible to do simple things like concrete scope restrictions and compilation, which leads to imminent scope bloat.

    Most of the time, people I see using ui-router are the same people brutalizing their applications constantly using expressions, emit, broadcast and everything else people do because they don’t really understand how their data is flowing.

    Fun experiment: build anything generic that wires up and mutates datatables.net or footable in ui router. You’re going to be hating life. You’ll go through lots of gyrations with data munging and triggers before you just resort to the ugliness of obliterating on every refresh.

  25. Scott July 10, 2015 at 8:03 am #

    I am working on a data visualization app with multiple data viewers, so seems like a natural for ui-router. You can tab back and forth between a table, map and graph of the same data (and use a control panel to determine which data are being displayed). The issue I have with routing is that changing views destroys the old view, which was expensive to create. When you go to a new map, for instance, it is reasonable to wait a short while for it to be constructed. But when you then flip to the table and back to the map, with nothing changed, it should come back instantly. Any thoughts?

    • Craig McKeachie July 10, 2015 at 10:28 am #

      Hi Scott, this is just quick off my head but should point you in the right direction. I agree that when you flip between table and map it should come back instantly. Couple thoughts: when you go from table to map of the same data perhaps these should be in the same controller and you should just hide and show the map or table using ng-if. I know you want your map logic and table logic separate so maybe they should each be a directive within a view for a dashboard widget (I could see that being reusable enough). Also consider that AngularJS services are singletons and that is where you should do your data access which could then be cached until you transition between routes or another sensible event for your requirements. But in general I’m not sure whether a given widget is showing a map or a table is something I would encode in the route. I think this state about the page might be better to just persist in the browsers memory in a service object so if the user navigates away and back it will remember but if you email a url this might be lost. If you haven’t already I would search for Angular Dashboard directives and at least see how other people are handling the problem. Here is one I found quickly but haven’t looked at the source yet. https://github.com/sdorra/angular-dashboard-framework

  26. Shrikantcghate July 16, 2015 at 4:39 am #

    ui.layout is ot working with UI-router , UI.layout split panel taking all screen space .Am facing issues about this so that my layout is not bound inside routed view. Also once I directed to Ui-layout page then can not go back as well. any help on this appreciated in advance. Thanks

  27. Shrikantcghate July 16, 2015 at 4:42 am #

    Another problem with UI-router is that ui-sref= here can not add values dynamically from controller so can not build dynamic routing frame work using UI-router.

  28. harsh August 13, 2015 at 6:31 pm #

    Best tutorial on ui-router!! thanks a lot..

  29. Mohd Junaid Khan September 29, 2015 at 8:56 am #

    Very clear and informative

  30. jeet October 17, 2015 at 12:28 am #

    Really well written and easy to understand.
    If we want to write separate controller for each template in separate folder then how can we make use of ui-router?

  31. jeet October 17, 2015 at 12:34 am #

    In other way, writing this same app https://github.com/angular/angular-seed/tree/master/app using ui-router.

  32. Jan November 22, 2015 at 10:33 am #

    Great post. I’m new to angular and was struggling with multiple views on page. This made it crystal clear.

  33. Beena November 26, 2015 at 8:33 am #

    Awesome Tutorial. Got a clear idea on the topic.

    Observation:
    .state(‘app.subscribers.detail’, {
    url: ‘/:id’,
    views: {
    ‘detail@app.subscribers’: {
    templateUrl: ‘templates/partials/subscriber-detail.html’,
    controller: ‘SubscriberDetailController’
    }
    }
    });

    {{subscriber.name}}
    {{subscriber.email}}

    The nested view worked after providing “app.subscribers.detail({id: subscriber.id})” in ui-sref.

  34. Steve December 6, 2015 at 10:25 am #

    It’s an interesting debate, ngRoute or ui-router – which you choose, I think; should be related to device capabilties.
    But, perhaps you can answer a question I have not as yet found a solution to.

    I have an app, which contains eight routes each containing its own template and controller.
    This app was designed ‘mobile-first’ and as a consequence my design looks and works perfectly on a ‘smartphone’.
    Now I want to increase device size, to a tablet or laptop, all my route dependancies are now redundant because, with a larger screen I could combine them into one controller and one template, and replace the original 4 ( 2 controllers, 2 templates ) for each pair of list/detail combinations.

    I’ve read in other places, that media queries are the way to go for differing resolutions, but this does not fix the controller/template problem. How can angularjs choose a route based on media-queries, it’s just not possible.

    Hopefully, you could help shed some light on this conundrum.

    Best Regards
    Steve

  35. Jitendra Bhargava December 12, 2015 at 6:28 am #

    Hi,

    I have multiple pages with each page having its own route and on dashboard i have functionality where its link to inner detail pages.

    How can i use ui-router between two states ??
    i am not able to send user from state one to state two route url.
    How can i do this ?

  36. Krishnakumar January 5, 2016 at 3:11 am #

    Excelant explanation. This is what I was expecting
    Thanks Craig.

  37. dreamy January 14, 2016 at 4:37 am #

    I really love your article as it helps me to understand the ui-routes but as I try to tweak some of the areas,
    I’m having problem with fetching JSON file and render it in shows-detail.html page. I don’t know how to modify the controller to make it specific for its $stateParam

    Here is my modified code :

    app.factory(‘ShowsService’,function(){
    return {
    list: $http.get(‘data.json’),
    find: function(id){
    return _.find(shows, function(show){return show.id == id});
    }
    }

  38. dreamy January 14, 2016 at 4:38 am #

    I really love your article as it helps me to understand the ui-routes but as I try to tweak some of the areas,
    I’m having problem with fetching JSON file and render it in shows-detail.html page. I don’t know how to modify the controller to make it specific for its $stateParam. I really appreciate any help

    Here is my modified code :

    app.factory(‘ShowsService’,function(){
    return {
    list: $http.get(‘data.json’),
    find: function(id){
    return _.find(shows, function(show){return show.id == id});
    }
    }

  39. Bill Sorensen January 22, 2016 at 9:42 am #

    Thank you. This is the only clear explanation I’ve found of view names.

    I think there’s a typo under State names:

    “If you need to find the content view-name in subscribers.html it would be:
    ‘detail@app.subscribers'”

    Shouldn’t this be the detail view-name?

    Also you might consider renaming the State names section (and the terms “state name” and “state-name” under it) to “View names”. That’s how the UI-Router docs refer to them: https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views

    Finally please consider submitting a pull request to UI-Router on GitHub for the above documentation page. Not understanding this cost me hours. You can explain this far more clearly than the original authors did.

    Thank you!

  40. Lionel Momméja February 7, 2016 at 2:56 pm #

    Craig,
    thanks a lot for this tutorial. Very clear.
    You’re definitely part of these people who are able to make complex things easy to understand.
    Cheers,
    Lionel

  41. Kuldip D Gandhi March 2, 2016 at 6:31 am #

    Fantastic Article on UI Router. Complex Concept made easy to understand.
    Thanks Craig.

  42. Dipak March 7, 2016 at 5:55 am #

    Awesome article ..
    Thank you Craig McKeachie.
    Such a nice article to explain with example in brief and very simple language rather than using complex and unnecessary technical words.
    Thank you very much.

  43. John Laine March 25, 2016 at 6:52 pm #

    Great article, well thought out and explained!

  44. Saeed D. August 8, 2016 at 8:01 pm #

    Thank you very much for this well written article.

Trackbacks/Pingbacks

  1. Dew Drop – December 8, 2014 (#1910) | Morning Dew - December 8, 2014

    […] UI-Router: Why many developers don’t use AngularJS’s built-in router (Craig McKeachie) […]

  2. angular UI to the rescue | Cautionary Tales of a darling do-gooder - December 20, 2014

    […] just stumbled upon this post by funnyant about using angularJS UI-router: https://www.funnyant.com/angularjs-ui-router/. I got five sentences in and had an epiphanic moment – I’m not alone in my tortured […]

  3. Angular ui-router | Longing to know - March 19, 2015

    […] http://www.funnyant.com/angularjs-ui-router/ […]

  4. How to configure ui-router with home/landing states and app states - HTML CODE - January 13, 2016

    […] i’m been using next tutorial guide: http://www.funnyant.com/angularjs-ui-router/ […]

  5. [ASK] programming - Angular-ui-router loading same template with different parameters | Some Piece of Information - February 18, 2016

    […] (The code is based on this sample: http://www.funnyant.com/angularjs-ui-router/) […]

  6. UI-Router: Why many developers don’t use AngularJS’s built-in router – Funny Ant | Layout - January 21, 2017

    […] Source: UI-Router: Why many developers don’t use AngularJS’s built-in router – Funny Ant […]

  7. Using UI router and views with angular multiple-views-sketch… – DevOps Infographics Resource Center – Programming and IT - February 6, 2017

    […] Source: funnyant.com […]

Leave a Reply