Skip to content
timkindberg edited this page Feb 15, 2013 · 50 revisions

Main Goal

To evolve the concept of an Angular "Route" into a more general concept of a "State" for managing coarse application UI states.

Main Features:

  1. A state manager $stateProvider and $state, keeps state logic separate from routing logic.
  2. Nested states (parent/child relationships).
  3. Can set multiple views via named views. ui-view directive.
  4. URL Routing
  5. Backwards compatible with Angular v1 router
  6. Various other nuggets of goodness

State Manager

The new $stateProvider works similar to Angular's v1 router, but it focuses purely on state.

  • A state corresponds to a "place" in the application in terms of the overall UI and navigation.
  • A state describes (via the controller / template / view properties) what the UI looks like and does at that place.
  • States often have things in common, and the primary way of factoring out these commonalities in this model is via the state hierarchy, i.e. parent/child states aka nested states.

The simplest form of state

A state in its simplest form can be added like this (typically within module.config):

<!-- in index.html -->
<body ng-controller="MainCtrl">
<section ui-view></section>
</body>
// in app-states.js (or whatever you want to name it)
$stateProvider.state('contacts', {
  template: '<h1>My Contacts</h1>'
}

// main-controller.js
function MainCtrl($state){
  $state.transitionTo('contacts');
}

The template is automatically placed into the lone ui-view when the state is transitioned to.

Alternative ways to set the Template

Instead of writing the template inline you can load a partial. This is probably how you'll set templates most of the time.

$stateProvider.state('contacts', {
  templateUrl: 'contacts.html'
}

Or you can use a template provider function which can be injected and has access to locals, like this:

$stateProvider.state('contacts', {
  templateProvider: function ($timeout) {
    return $timeout(function () { return '<h1>My Contacts</h1>' }, 100);
  }
}

Controllers

You can pair a template with a controller like this:

$stateProvider.state('contacts', {
  template: '<h1>{{title}}</h1>',
  controller: function($scope){
    $scope.title = 'My Contacts';
  }
}

Resolve

You can use resolve to provide your controller with content or data custom to the state. This allows you to reuse controllers for similar objects that needs different data.

$stateProvider.state('contacts', {
  template: '<h1>{{title}}</h1>',
  resolve: { title: 'My Contacts' },
  controller: function($scope, title){
    $scope.title = title;
  }
}

onEnter and onExit callbacks

There are also optional 'onEnter' and 'onExit' callbacks that get called when a state becomes active and inactive respectively. The callbacks also have access to all the resolved dependencies.

$stateProvider.state("contacts", {
  template: '<h1>{{title}}</h1>',
  controller: function($scope, title){
    $scope.title = 'My Contacts';
  },
  onEnter: function(..what to inject?...){
    ... need simple useful example here ...
  },
  onExit: function(..what to inject?...){
    ... need simple useful example here ...
  }
}

Nested states

States can be nested within each other. You can specify nesting in several ways:

You can use dot syntax to infer your heirarchy to the $stateProvider. Below, contacts.list becomes a child of contacts.

$stateProvider
  .state('contacts', {});
  .state('contacts.list', {});

Alternately, you can use specify the parent of a state via the parent property. IS THIS RIGHT?

$stateProvider
  .state('contacts', {});
  .state('list', {
    parent: 'contacts'
  });

Finally, if you aren't fond of using string-based states, you can also use object-based states, like this:

var states = {};
states.contacts = { 
    name: 'contacts',
    templateUrl: 'contacts.html'    
}
states.contacts.list = { 
    name: 'list',
    parent: states.contacts, //parent is required in this method
    templateUrl: 'contacts.list.html'
}

$stateProvider
  .state(states.contacts);
  .state(states.contacts.list)

If you use object-based states, you can usually reference the object directly when using other methods and property comparisons:

$state.transitionTo(states.contacts);
$state.self === states.contacts;
$state.includes(states.contacts)

Nested views

Child states will load their templates into their parent's ui-view.

$stateProvider
  .state('contacts', {
    templateUrl: 'contacts.html'
    controller: function($scope){
      $scope.contacts = [{ name: 'Alice' }, { name: 'Bob' }];
    }
  });
  .state('contacts.list', {
    templateUrl: 'contacts.list.html';
  });

function MainCtrl($state){
  $state.transitionTo('contacts.list');
}
<!-- index.html -->
<body ng-controller="MainCtrl">
  <div ui-view></div>
</body>
<!-- contacts.html -->
<h1>My Contacts</h1>
<div ui-view></div>
<!-- contacts.list.html -->
<ul>
  <li ng-repeat="contact in contacts">
    <a>{{contact.name}}</a>
  </li>
</ul>

Inheritance from parent states

When the application is in a particular State (aka when a state is "active"), all it's ancestor states are implicitly active as well. In the sample, when "contacts.list" state is active, the "contacts" state is implicitly active as well. Child states inherit views (templates/controllers) and resolved dependencies from parent state(s), which they can override.

Here contacts.list is inheriting its controller from contacts:

$stateProvider
  .state('contacts', {
    template: '<h1>My Contacts</h1>'
    controller: function($scope){
      $scope.contacts = [{ name: "Alice" }, { name: "Bob" }];
    }
  });
  .state('contacts.list', {
    template: '<ul><li ng-repeat="contact in contacts">' +
                 '<a>{{contact.name}}</a>' +
              '</li></ul>';
  });

Multiple Named Views

You can name your views so that you can have more than one ui-view per state. When setting multiple views you need to use the views property on state. views is an object. The property keys on views should match your view names, like so:

<!-- somereportthing.html -->
<body>
  <div ui-view="filters"></div>
  <div ui-view="tabledata"></div>
  <div ui-view="graph"></div>
</body>
$stateProvider
  .state('report', {
    views: {
      'filters': { ... templates, controllers, resolve, etc ... },
      'tabledata': {},
      'graph': {},
    }
  })

Then each view in views is can set up its own templates, controllers, and resolve data.

$stateProvider
  .state('report',{
    views: {
      'filters': {
        templateUrl: 'report-filters.html',
        controller: function($scope){ ... controller stuff just for filters view ... }
      },
      'tabledata': {
        templateUrl: 'report-table.html',
        controller: function($scope){ ... controller stuff just for tabledata view ... }
      },
      'graph': {
        templateUrl: 'report-graph.html',
        controller: function($scope){ ... controller stuff just for graph view ... }
      },
    }
  })

URL Routing

Parameter Options

Clone this wiki locally