ui-router

a brief intro + what you're missing by not using it RIGHT NOW!

Aaron Smith - CS Major 
Dave Ackerman - Not a CS Major

Angular Routing without UI-Router




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


<html ng-app><body>
<div>Some static header perhaps</div> <div ng-view> <!-- Where the "magic" happens --> </div> <div>Obligatory privacy policy and copyright.</div></body></html>

This sounds great!

Well, not so fast...

ngRoute is limiting



views: there can be only one ngView (at a time)

resolves: nestable? not so much.

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.8 (latest stable) released in Jan 2014

UI-Router @ 10,000ft



  • Multiple, concurrent views
  • State-driven
  • Nested views
  • Nested resolves
  • onEnter/onExit events

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) {
            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: { title: 'My Contacts' },
  onEnter: function(title){
    if(title){ ... do something ... }
  },
  onExit: function(title){
    if(title){ ... 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',
  data: { pageTitle: 'Home' },
  views: {
    'main': {
      templateUrl: 'home/home.tpl.html'
    }
  }
})
.state('home.confirm', {
  url: '/confirm',
  data: { pageTitle: '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?

Copy of Copy of Copy of Intro to AngularJS

By dmackerman

Copy of Copy of Copy of Intro to AngularJS

Small intro to AngularJS to accompany a code demo. Presented at the Montgomery County Java Users Group on October 16th in Rockville, MD.

  • 1,281