AngularJS Application Routing Tuesday, 13 August 13
Routes: REST/Servers v. Client Apps •HTTP is stateless, so routes help define application state. •REST emphasizes semantic routes. (GET  /user/dave) But... •There is more state than can be represented in a route (user state, session state) •Client-side apps don’t actually need routes to work (e.g. many jquery-based apps) Tuesday, 13 August 13
Why use routes? Routes: •Make the back button work as expected •Make major (page-like) transitions clear •Allow easy linking •Make testing easier •Encapsulate some state Tuesday, 13 August 13
From Angular-Phonecat: angular.module('phonecat',  ['phonecatFilters',   'phonecatServices']).    config(['$routeProvider',  function($routeProvider)  {    $routeProvider.            when('/phones',  {templateUrl:  'partials/phone-­‐ list.html',      controller:  PhoneListCtrl}).            when('/phones/:phoneId',  {templateUrl:  'partials/ phone-­‐detail.html',  controller:  PhoneDetailCtrl}).            otherwise({redirectTo:  '/phones'}); }]); Basic Routes Tuesday, 13 August 13
.when('/phones/:phoneId',  {templateUrl:  'partials/phone-­‐ detail.html',  controller:  PhoneDetailCtrl}) :phoneId stored in $route.current.params, available controller and resolve functions. Basic Routes Tuesday, 13 August 13
Realistic Routes Structure / – welcome /stream – logged-in home /login – log-in /signup – signup /user/:username – user profile /explore – explore tags /settings – user settings /new-spark – create spark page /spark/cluster?sparkName=&tagName – spark cluster page /search – search /spark/:username/:slug – spark page ...and 11 more routes Tuesday, 13 August 13
Resolve for Data Each item in the resolve map: •Runs before the controller is instantiated •Provides its value to the controller •If it is a promise: •It will (wait to) resolve the promise first •If the promise is rejected, the route errors Using resolve for database objects means •No UI flicker as promises are resolved •Controllers can assume objects exist •Tests are cleaner: pass in data object explicitly Tuesday, 13 August 13
Resolve for Data when('/tag/:tagName',  {  resolve:  {    tagResolve:  ['tag','$route',      function  (tagService,  $route)  {        return  tagService.get($route.current.params.tagName);    }],    sparkClustersResolve:  ['sparkCluster','$route',      function  (sparkClusterService,  $route)  {        return   sparkClusterService.getSparkClustersByTag($route.current.p arams.tagName); }]}}); Tuesday, 13 August 13
Resolve for Data Treat your resolved object like a dependency in the controller: .controller('ViewTagCtrl',  ['$scope','$routeParams','tag', 'sparkCluster','log','$location','user','header','sparkClu stersResolve','tagResolve','currentUserResolve' Use a ‘route controller’, not a ‘view controller’ (ng-­‐controller): when('/tag/:tagName',  {  controller:  'ViewTagCtrl'  }) Tuesday, 13 August 13
Resolve for Rules Rejected Promises cause routes to fail. You can use this to make rules for the route like ACLs or prerequisites. Reject a promise to cause a route to fail. Using resolve to make rules means: •All resolution rules must pass before route succeeds and controller is instantiated. •Common ACLs (logged-in) specified in routes, not each controller or service •Can redirect the user to an appropriate page (also can do user-facing error) Tuesday, 13 August 13
when('/tag/:tagName',  {  resolve:  {    mustAuth  :  ['route',  function  (routeService)  {        return  routeService.mustAuth('/');}]}]}}); routeService.mustAuth  =  function  (redirectTo)  {  var  authDeferred,  p;  authDeferred  =  $q.defer();  p  =  userService.getCurrent();  p.then(function  (currentUser)  {    if  (angular.isUndefined(currentUser._id))  {      $location.url(redirectTo);      authDeferred.reject();    }  else  {  authDeferred.resolve(mustAuth);  }});  return  authDeferred.promise; }; Resolve for Rules Tuesday, 13 August 13
Resolve for Route-Specific UI Resolve can pass route-specific configuration to another service that affects the UI, like “show a promo”. Using resolve to control UI means: •Routes handle turning on and off UI elements •Route Controllers don’t need to worry about configuring site- wide UI elements like headers, promos, menus *There are other (possibly better) options for this, including having the UI controller listen to RouteChangeSuccess events. Tuesday, 13 August 13
when('/tag/:tagName',  {  resolve:  {    makeSparkPromo:  ['promo',      function  (promoService)  {        return  promoService.route('makeSparkPromo',  true); }]}}}); Resolve for Route-Specific UI Tuesday, 13 August 13
Testing Routes Midway Normally you can only test routing with E2E testing, slowly. Midway Testing (see Year Of Moo) is basically big unit testing. Use a router helper, which has routeDefined, getRoute. Midway testing routes means unittesting-like speed and E2E-like app setup. Tuesday, 13 August 13
ROUTER.when('tag',  '/tag/:tagName',  {  templateUrl:  '/partials/tag/tag-­‐detail.html',  controller:  'ViewTagCtrl' }); ROUTER.otherwise({  redirectTo  :  '/' }); ROUTER.install($routeProvider); Testing Routes Midway–Router Helper Tuesday, 13 August 13
before(function(done)  {  test  =  new  ngMidwayTester();  test.register('App',  done); }); it('should  have  a  route  to  a  tag  page',  function()  {        expect(  ROUTER.routeDefined('tags')).to.equal(true);  var  url  =  ROUTER.routePath('tags',  {tagName:  ‘cooking’);  expect(url).to.equal('/tag/cooking'); }); Testing Routes Midway–Testing Tuesday, 13 August 13
Recap The phonecat way (sticking everything under app) is unscalable. Angular’s route system is flexible and powerful, if you take advantage of resolve and promises. Using helper functions and midway testing means you can unit test more things. The less you rely on E2E testing, the better. Tuesday, 13 August 13
Other Good Ideas Others have addressed the same problems in a more reusable way. Angular UI router: use ‘state’ instead of routes. Seems good. Worth considering if you start fresh. Angular App: great example of well-designed app, but maybe hard to follow for a novice. Interesting crudRouteProvider implementation. Tuesday, 13 August 13

Angular JS Routing

  • 1.
  • 2.
    Routes: REST/Servers v.Client Apps •HTTP is stateless, so routes help define application state. •REST emphasizes semantic routes. (GET  /user/dave) But... •There is more state than can be represented in a route (user state, session state) •Client-side apps don’t actually need routes to work (e.g. many jquery-based apps) Tuesday, 13 August 13
  • 3.
    Why use routes? Routes: •Makethe back button work as expected •Make major (page-like) transitions clear •Allow easy linking •Make testing easier •Encapsulate some state Tuesday, 13 August 13
  • 4.
    From Angular-Phonecat: angular.module('phonecat',  ['phonecatFilters',   'phonecatServices']).    config(['$routeProvider',  function($routeProvider)  {    $routeProvider.            when('/phones',  {templateUrl:  'partials/phone-­‐ list.html',      controller:  PhoneListCtrl}).            when('/phones/:phoneId',  {templateUrl:  'partials/ phone-­‐detail.html',  controller:  PhoneDetailCtrl}).            otherwise({redirectTo:  '/phones'}); }]); Basic Routes Tuesday, 13 August 13
  • 5.
    .when('/phones/:phoneId',  {templateUrl:  'partials/phone-­‐ detail.html',  controller:  PhoneDetailCtrl}) :phoneId stored in $route.current.params, available controller and resolve functions. Basic Routes Tuesday, 13 August 13
  • 6.
    Realistic Routes Structure /– welcome /stream – logged-in home /login – log-in /signup – signup /user/:username – user profile /explore – explore tags /settings – user settings /new-spark – create spark page /spark/cluster?sparkName=&tagName – spark cluster page /search – search /spark/:username/:slug – spark page ...and 11 more routes Tuesday, 13 August 13
  • 7.
    Resolve for Data Eachitem in the resolve map: •Runs before the controller is instantiated •Provides its value to the controller •If it is a promise: •It will (wait to) resolve the promise first •If the promise is rejected, the route errors Using resolve for database objects means •No UI flicker as promises are resolved •Controllers can assume objects exist •Tests are cleaner: pass in data object explicitly Tuesday, 13 August 13
  • 8.
    Resolve for Data when('/tag/:tagName',  {  resolve:  {    tagResolve:  ['tag','$route',      function  (tagService,  $route)  {        return  tagService.get($route.current.params.tagName);    }],    sparkClustersResolve:  ['sparkCluster','$route',      function  (sparkClusterService,  $route)  {        return   sparkClusterService.getSparkClustersByTag($route.current.p arams.tagName); }]}}); Tuesday, 13 August 13
  • 9.
    Resolve for Data Treatyour resolved object like a dependency in the controller: .controller('ViewTagCtrl',  ['$scope','$routeParams','tag', 'sparkCluster','log','$location','user','header','sparkClu stersResolve','tagResolve','currentUserResolve' Use a ‘route controller’, not a ‘view controller’ (ng-­‐controller): when('/tag/:tagName',  {  controller:  'ViewTagCtrl'  }) Tuesday, 13 August 13
  • 10.
    Resolve for Rules RejectedPromises cause routes to fail. You can use this to make rules for the route like ACLs or prerequisites. Reject a promise to cause a route to fail. Using resolve to make rules means: •All resolution rules must pass before route succeeds and controller is instantiated. •Common ACLs (logged-in) specified in routes, not each controller or service •Can redirect the user to an appropriate page (also can do user-facing error) Tuesday, 13 August 13
  • 11.
    when('/tag/:tagName',  {  resolve:  {    mustAuth  :  ['route',  function  (routeService)  {        return  routeService.mustAuth('/');}]}]}}); routeService.mustAuth  =  function  (redirectTo)  {  var  authDeferred,  p;  authDeferred  =  $q.defer();  p  =  userService.getCurrent();  p.then(function  (currentUser)  {    if  (angular.isUndefined(currentUser._id))  {      $location.url(redirectTo);      authDeferred.reject();    }  else  {  authDeferred.resolve(mustAuth);  }});  return  authDeferred.promise; }; Resolve for Rules Tuesday, 13 August 13
  • 12.
    Resolve for Route-SpecificUI Resolve can pass route-specific configuration to another service that affects the UI, like “show a promo”. Using resolve to control UI means: •Routes handle turning on and off UI elements •Route Controllers don’t need to worry about configuring site- wide UI elements like headers, promos, menus *There are other (possibly better) options for this, including having the UI controller listen to RouteChangeSuccess events. Tuesday, 13 August 13
  • 13.
    when('/tag/:tagName',  {  resolve:  {    makeSparkPromo:  ['promo',      function  (promoService)  {        return  promoService.route('makeSparkPromo',  true); }]}}}); Resolve for Route-Specific UI Tuesday, 13 August 13
  • 14.
    Testing Routes Midway Normallyyou can only test routing with E2E testing, slowly. Midway Testing (see Year Of Moo) is basically big unit testing. Use a router helper, which has routeDefined, getRoute. Midway testing routes means unittesting-like speed and E2E-like app setup. Tuesday, 13 August 13
  • 15.
    ROUTER.when('tag',  '/tag/:tagName',  {  templateUrl:  '/partials/tag/tag-­‐detail.html',  controller:  'ViewTagCtrl' }); ROUTER.otherwise({  redirectTo  :  '/' }); ROUTER.install($routeProvider); Testing Routes Midway–Router Helper Tuesday, 13 August 13
  • 16.
    before(function(done)  {  test  =  new  ngMidwayTester();  test.register('App',  done); }); it('should  have  a  route  to  a  tag  page',  function()  {        expect(  ROUTER.routeDefined('tags')).to.equal(true);  var  url  =  ROUTER.routePath('tags',  {tagName:  ‘cooking’);  expect(url).to.equal('/tag/cooking'); }); Testing Routes Midway–Testing Tuesday, 13 August 13
  • 17.
    Recap The phonecat way(sticking everything under app) is unscalable. Angular’s route system is flexible and powerful, if you take advantage of resolve and promises. Using helper functions and midway testing means you can unit test more things. The less you rely on E2E testing, the better. Tuesday, 13 August 13
  • 18.
    Other Good Ideas Othershave addressed the same problems in a more reusable way. Angular UI router: use ‘state’ instead of routes. Seems good. Worth considering if you start fresh. Angular App: great example of well-designed app, but maybe hard to follow for a novice. Interesting crudRouteProvider implementation. Tuesday, 13 August 13