UI-Router

http://slides.com/ansmith/ui-router-intro/live

Aaron Smith    @aaron_n_smith
Dave Ackerman @dmackerman

Angular Routing without UI-Router

(ngRoute)
ngRoute, the default routing solution, comprises:

  • $routeProvider
  • $route
  • $routeParams
  • ngView
  • works with $location

ngRoute Configuration


angular.module('myApp', ['ngRoute'])

.config(function($routeProvider) {
  $routeProvider.when('/home', {
    templateUrl: 'views/home.html',
    controller: 'HomeController'
  })
  .when('/detail', {
    templateUrl: 'views/detail.html',
    controller: 'DetailController'
  })
  .otherwise('/home');
});

ngRoute Parameters


angular.module('myApp', ['ngRoute'])

.controller('DetailCtrl', function($scope, $routeParams) {
  $scope.record = $http.get('api/' + $routeParams.recordId);
});

.config(function($routeProvider) {
  $routeProvider.when('/detail/:recordId', {
    templateUrl: 'views/detail.html',
    controller: 'DetailCtrl'
  });
});

ngRoute Resolves


angular.module('myApp', ['ngRoute'])

.controller('DetailCtrl', function($scope, record) {
  $scope.record = record;
})

.config(function($routeProvider) {
  $routeProvider.when('/detail/:recordId', {
      templateUrl: 'views/detail.html',
      controller: 'DetailCtrl',
      resolve: {
        record: function($route, $http) {
          var recId = $route.current.params.recordId;
          return $http.get('api/' + recId);
        }
      }
    });
});

ngView directive


<!DOCTYPE html>
<html ng-app>
<head></head>
<body>
    <header>Some static header perhaps</header>
    <div ng-view>
        <!-- your route here -->
    </div>
    <footer>Obligatory privacy policy and copyright.</footer>
</body>
</html>

This sounds great!

Well, not so fast...

ngRoute is limiting



  • views: there can be only one ngView
  • resolves: must be independent

UI-Router addresses ngRoute's deficiencies...


and much, much more.

UI-Router: a little history



  • Part of the AngularUI umbrella project
  • Karsten Sperling, Tim Kindberg, Jens Melgaard
  • Started in early 2013
  • 0.0.1 released in May 2013
  • 0.2.10 (latest stable) release

UI-Router @ 10,000ft



  • Multiple, concurrent views
  • State-driven
  • Nested views
  • Nested resolves
  • onEnter/onExit events (+ others)

ngRoute vs. UI-Router


$routeProvider
$route
$routeParams
ng-view
href
$urlRouterProvider
$state
$stateParams
ui-view
ui-sref  *

Some "Advanced" Concepts


  • Inheriting resolved dependencies to child states.
  • Splitting UI elements into their own components
  • Preventing access to a particular state (authenticated states)
  • View loading events
  • Named views


Inherited Resolve Dependencies


$stateProvider.state('feed', {
    url: '/feed'
    resolve: {
        'feedData': function() {
            return 'foo';
        }
    }
})
.state('feed.detail', {
    url: '/feed/:itemId'
    resolve: {
        'singleFeedData': function(feedData, $state) {            // can access the current state with             $state.current...   // $state object.
            return feedData;  // the inherited resolve.
        }
    }
});

Breaking Up Your $stateProvider


  • Long $stateProviders are hard to read!
  • Break your apps states into modules which share common functionality (your code should be doing this anyway!)


angular.module('main', ['main.contacts', 'ui.router'])
.config(function($stateProvider) {
    $stateProvider.state('main', {      ...    })
});
 
angular.module('main.contacts', ['ui.router'])
.config(function($stateProvider) {
    $stateProvider.state('main.contacts', {
      ...
    })
});

Events


$scope.$on('$stateChangeError', function(event, toState, ... , error) {    // catch errors from resolves in here!
});

  • One of the most useful events for detecting errors in resolved dependencies.
  • Also useful for things such as loading masks / indicators.

View Events

Happen when a state becomes active and inactive.

$stateProvider.state("contacts", {
  templateUrl: 'contacts.tpl.html',
  resolve: { data: function() { return 'My Contacts'; },  onEnter: function(data){
    if(data){ ... do something ... }
  },
  onExit: function(title){
    if(data){ ... do something ... }
  }
})

Stopping a state transition 

(with Authentication)


$stateProvider.state('admin', {
    url: '/admin',
    data: {   
        pageTitle: 'Admin Console',
        requiresAuth: true
    }
    ...
})

$scope.$on('$stateChangeStart', function(event, toState, toParams... )
    
    // check authentication in your "auth service".
    if (toState.data.requiresAuth && !AuthService.isAuthenticated()) {
        $state.transitionTo('login');
        event.preventDefault(); // stop state transition
    }
   

});

Named Views


<header ui-view="header"></header>
<nav ui-view="switcher"></nav>
<div ui-view="menu" autoscroll="false"></div>

  • Powerful concept, but use with caution.
  • Not every section in your app needs to be named.

Named Views (cont)


$stateProvider.state('home', {
  url: '/home',
  views: {
    'main': {
      templateUrl: 'home/home.tpl.html'
    }
  }
})
.state('home.confirm', {
  url: '/confirm',
  views: {
    // target and override the named view "main" in the parent state
    'main@': {
      templateUrl: 'home/confirm.tpl.html'
    }
  }
});

  • viewName@stateName for targetting.

Don't overcomplicate.


Abstract States


  • Useful if you need to prepend a URL to your child states.
  • Also useful for prepopulating child resolves with data.

// cannot activate this state directly.
.state('parent', { url: '/home', abstract: true })

// ALSO url '/home', overriding its parent's activation
.state('parent.list', { url: '', abstract: true })

Tips & Tricks


  • Putting $state  and $stateParams  on your $rootScope/$scope can be useful for state dependent changes on your UI.

  • Always (read: ALWAYS) include a $stateChangeError listener in your app.

  • Use autoscroll="false" on your ui-views to avoid saving scroll position. 

  • States don't need URLs. Useful for things like modals, asides - things that are global but not tied directly to states.

  • Be careful with state naming, and be aware of targeting. Don't overnest states. Avoid things like:  app.home.contacts.details.add

Animations!

because everyone loves animations.


  • Really easy with the new version of ngAnimate.
  • Check out: http://mgcrea.github.io/angular-motion/
  • Make sure your content areas have a height specified or things will be janky.

ui-sref-active

  • By default, doesn't work for nested states.

  • Workaround!!
     <a ng-class="{ active: $state.includes('app.dashboard')}" ui-sref="app.dashboard">Dashboard</a>
  • Only works if you put $state on your $scope.

Other useful stuff.


  • Modals tied to a state? Use onEnter  and  onExit  to open/close them. 
  • Animation is relatively painless with newer versions of  ngAnimate
  • $state.reload()  - resolves are re-resolved, events are not re-fired, and controllers are re-instantiated
  • You can use regex's in URL matching for states.
  •  url: '/{userId:[0-9]{1,4}}',

Demo App




The FUTURE


The Angular team is very aware the current
routing solution is sub-par.


And they're going to fix it!


.... in 2.0

Thanks!


Questions?

Aaron Smith    @aaron_n_smith
Dave Ackerman @dmackerman

UI-Router Intro

By Aaron N. Smith

UI-Router Intro

Dive into AngularJS routing with UI-Router. Overview and comparison of ngRoute and UI-Router as well as tips, tricks and usage recommendations for UI-Router.

  • 3,683