Learn UI-Routing Series - Part Two - States

Learn Angular UI-Routing. In this article, we will have an in-depth learning for state in ui-router.

Introduction

This is the second article in series of articles for learning UI-Router. In this article, we will have an in-depth learning for state in ui-router.

In case you have not read previous article, you can find it Here,

Overview

What is ui-Router state?

A state normally references a “place” in an application where all the information of UI and Navigation exists. A state describes the UI of an application via the Controller/ template / view properties.

A state is a JavaScript object which has specific Properties. Those properties help in defining the functionality of application when a state is active, below are the properties of state.

  • name: A name for the state, providing a way to refer to the state
  • views: How the UI will look and behave
  • url: What the browser’s URL will be
  • prams: Parameter values that the state requires (such as blog-post-id)
  • resolve: The actual data the state requires (often fetched, asynchronously, from the backend using a parameter value)

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

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

Where is the Template viewed?

This is the question which will be raised in everyone’s mind. Whenever a state is activated the template is automatically rendered in the ui-view of its parent state’s template. If it's a top-level state—which 'contacts' is because it has no parent state–then its parent template is index.html.

Here in our example, the contacts state won’t ever be activated. So, let’s see how we can activate a state.

Activating a state

There are mainly three methods to activate a state as listed below.

  1. Call $state.go(). High-level convenience method.
  2. Click on the link written in ui-sref
  3. Navigate to URL associated with state.

All three methods are explained in detail below.

$state.go()

$state

$state service is used for representing states and transition between them. It also provides information about the current state or even the states from which the transition is done.

$state.go(to, params, options)

ParamTypeDetails
tostring
Absolute state name or relative state path. Some examples:·
  • $state.go('contact.detail') - will go to the contact.detail state·
  • $state.go('^') - will go to a parent state·
  • $state.go('^.sibling') - will go to a sibling state·
  • $state.go('.child.grandchild') - will go to grandchild state
params(optional)objectA map of the parameters that will be sent to the state, will populate $stateParams. Any parameters that are not specified will be inherited from currently defined parameters. Only parameters specified in the state definition can be overridden, new parameters will be ignored.
options(optional)object
Options object. The options are:·
  • location - {boolean=true|string=} - If true will update the url in the location bar, if false will not. If string, must be "replace", which will update url and replace last history record.·
  • inherit - {boolean=true}, If true will inherit url parameters from current url.·
  • relative - {object=$state.$current}, When transitioning with relative path (e.g '^'), defines which state to be relative from.·
  • notify - {boolean=true}, If true will broadcast $stateChangeStart and $stateChangeSuccess events.·
  • reload (v0.2.5) - {boolean=false|string|object}, If true will force transition even if no state or params have changed. It will reload the resolves and views of the current state and parent states. If reload is a string (or state object), the state object is fetched (by name, or object reference); and \ the transition reloads the resolves and views for that matched state, and all its children states.

ui-sref

It is a directive that binds a link(<a>) to a state. If there is an associated url in the state then the directive will automatically generate and update the href attribute using $state.href method.

Usage

  • ui-sref='stateName' - Navigate to state, no params. 'stateName' can be any valid absolute or relative state, following the same syntax rules as $state.go()
  • ui-sref='stateName({param: value, param: value})' - Navigate to state, with params.

Example

Template HTML

  1. <a ui-sref="home">Home</a> | <a ui-sref="about">About</a>  
  2.   
  3. <ul>  
  4.     <li ng-repeat="contact in contacts">  
  5.         <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>  
  6.     </li>  
  7. </ul>   

URL

It is similar to the url that we have used in ngRoute

Here's how you set a basic url.

  1. $stateProvider .state('contacts', { url: "/contacts", templateUrl: 'contacts.html' })  

Now when the user accesses index.html/contacts then the 'contacts' state would become active and the main ui-view will be populated with the 'contacts.html' partial.

Templates

There are several methods to configure states template.

As seen in above example the simplest way is to set template by template config property

  1. $stateProvider.state('contacts', { template: '<h1>My Contacts</h1>'})  

Instead of writing template inline you can load a partial html page and probably it is the method that is used normally to insert template in a view.

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

templateUrl can also be a function with a predefined parameter $stateParams which is not injected. This function returns URL.

  1. $stateProvider.state('contacts', {  
  2.                      templateUrl: function ($stateParams){  
  3.                                         return '/partials/contacts.' + $stateParams.filterBy + '.html';  
  4.                       }  
  5. })  

you can also customize it by using templateProvider which can be injected, has access to locals and must return template HTML as below,

  1. $stateProvider.state('contacts', {  
  2.                        templateProvider: function ($timeout, $stateParams) {  
  3.                                          return $timeout(function () {  
  4.                                                        return '<h1>' + $stateParams.contactId + '</h1>'  
  5.                                          }, 100);  
  6.                        }  
  7. })  

Controllers

You can assign a controller to your template.

Note

The controller will not be instantiated if template is not defined.

You set your controller like this,

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

Or if you have already defined a controller in module, you can use it as below

  1. $stateProvider.state('contacts', {  
  2.                template: ...,  
  3.                controller: 'ContactsCtrl'  
  4. })  

If you want to define some random function in state itself and then assigning a controller name you can use it as below

  1. $stateProvider.state('contacts', {  
  2.               template: '<h1>{{contact.title}}</h1>',  
  3.               controller: function(){  
  4.                              this.title = 'My Contacts';  
  5.                          },  
  6.              controllerAs: 'contact'  
  7. })  

Or you can also write it as following

  1. $stateProvider.state('contacts', {  
  2.                template: ...,  
  3.                controller: 'ContactsCtrl as contact'  
  4. })  

Or for more advanced needs you can use the controllerProvider to dynamically return a controller function or string for you

  1. $stateProvider.state('contacts', {  
  2.                 template: ...,  
  3.                 controllerProvider: function($stateParams) {  
  4.                                  var ctrlName = $stateParams.type + "Controller";  
  5.                                 return ctrlName;  
  6.                  }  
  7.  })  

Controllers are instantiated on an as-needed basis, when their corresponding scopes are created, i.e. when the user manually navigates to a state via a URL, $stateProvider will load the correct template into the view, then bind the controller to the template's scope.

Resolve

If you want to provide data or some content that is custom to the state then you can use resolve. Resolve is an optional map of dependencies that should be injected to the controller.

If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.

The resolve property is a map object which contains key/value pairs of

  • key – {string}: a name of a dependency to be injected into the controller.
  • factory - {string|function}

    • If string, then it is an alias for a service.
    • Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before the controller is instantiated and its value is injected into the controller.
Examples

Each of the objects in resolve below must be resolved (via deferred.resolve() if they are a promise) before the controller is instantiated. Notice how each resolve object is injected as a parameter into the controller.

  1. $stateProvider.state('myState', {  
  2.       resolve:{  
  3.   
  4.          // Example using function with simple return value.  
  5.          // Since it's not a promise, it resolves immediately.  
  6.          simpleObj:  function(){  
  7.                     return {value: 'simple!'};  
  8.          },  
  9.   
  10.          // Example using function with returned promise.  
  11.          // This is the typical use case of resolve.  
  12.          // You need to inject any services that you are  
  13.          // using, e.g. $http in this example  
  14.          promiseObj:  function($http){  
  15.                        // $http returns a promise for the url data  
  16.                        return $http({method: 'GET', url: '/someUrl'});  
  17.          },  
  18.   
  19.          // Another promise example. If you need to do some   
  20.          // processing of the result, use .then, and your   
  21.          // promise is chained in for free. This is another  
  22.          // typical use case of resolve.  
  23.          promiseObj2:  function($http){  
  24.             return $http({method: 'GET', url: '/someUrl'})  
  25.                         .then (function (data) {  
  26.                                       return doSomeStuffFirst(data);  
  27.                            });  
  28.           },          
  29.   
  30.          // Example using a service by name as string.  
  31.          // This would look for a 'translations' service  
  32.          // within the module and return it.  
  33.          // Note: The service could return a promise and  
  34.          // it would work just like the example above  
  35.                  translations: "translations",  
  36.   
  37.          // Example showing injection of service into  
  38.          // resolve function. Service then returns a  
  39.          // promise. Tip: Inject $stateParams to get  
  40.          // access to url parameters.  
  41.          translations2: function(translations, $stateParams){  
  42.                              // Assume that getLang is a service method  
  43.                             // that uses $http to fetch some translations.  
  44.                            // Also assume our url was "/:lang/home".  
  45.                             return translations.getLang($stateParams.lang);  
  46.          },  
  47.   
  48.          // Example showing returning of custom made promise  
  49.          greeting: function($q, $timeout){  
  50.                            var deferred = $q.defer();  
  51.                           $timeout(function() {  
  52.                                   deferred.resolve('Hello!');  
  53.                           }, 1000);  
  54.                          return deferred.promise;  
  55.                     }  
  56.          },  
  57.   
  58.       // The controller waits for every one of the above items to be  
  59.       // completely resolved before instantiation. For example, the  
  60.       // controller will not instantiate until promiseObj's promise has   
  61.       // been resolved. Then those objects are injected into the controller  
  62.       // and available for use.    
  63.       controller: function($scope, simpleObj, promiseObj, promiseObj2, translations, translations2, greeting){  
  64.           $scope.simple = simpleObj.value;  
  65.   
  66.           // You can be sure that promiseObj is ready to use!  
  67.           $scope.items = promiseObj.data.items;  
  68.           $scope.items = promiseObj2.items;  
  69.   
  70.           $scope.title = translations.getLang("english").title;  
  71.           $scope.title = translations2.title;  
  72.   
  73.           $scope.greeting = greeting;  
  74.       }  
  75.    })  

Attach Custom Data to State Object

You can attach custom data to the state object so data passing during initiation of state is easy.
  1. // Example shows an object-based state and a string-based state  
  2. var contacts = {   
  3.     name: 'contacts',  
  4.     templateUrl: 'contacts.html',  
  5.     data: {  
  6.         customData1: 5,  
  7.         customData2: "blue"  
  8.     }    
  9. }  
  10. $stateProvider  
  11.   .state(contacts)  
  12.   .state('contacts.list', {  
  13.     templateUrl: 'contacts.list.html',  
  14.     data: {  
  15.         customData1: 44,  
  16.         customData2: "red"  
  17.     }   
  18.   })  
  19.   
  20. With the above example states you could access the data like this  
  21. function Ctrl($state){  
  22.     console.log($state.current.data.customData1) // outputs 5;  
  23.     console.log($state.current.data.customData2) // outputs "blue";  
  24. }  

Conclusion

Finally, we can say UI-Router provides more features then ngRoute. In next part, we will cover Transition and some features of UI-Router.