Angular.js

AngularJS UI-Router – Components

This is the second post of a series of 3, presenting AngularJS UI-Router library:

 
 
 
 

Components

As stated in the introduction to this routing library, the AngularJS UI-Router is built around states which allow us to have states bound to URLs like ngRoute module, but also leave us to have nested states and states with multiple views which is a very powerful feature we can take advantage in our applications. This is thanks to ui-router is based on states, not just a URL.

AngularJS Ui-Router provides a set of components we can use to create powerful, maintainable and well-structured applications:

  • $state/$stateProvider: Manages state definitions, the current state, and state transitions. This includes triggering transition-related events and callbacks, asynchronously resolving any dependencies of the target state, and updating $location to reflect the current state. For states that have URLs, a rule is automatically registered with $urlRouterProvider that performs a transition to that state
  • ui-sref directive: Equivalent to href or ng-href in <a /> elements except the target value is a state name. Adds an appropriate href to the element according to the state associated URL
  • ui-view directive: Renders views defined in the current state. Essentially ui-view directives are (optionally named) placeholders that gets filled with views defined in the current state
  • $urlRouter/$urlRouterProvider: Manages a list of rules that are matched against $location whenever it changes

Let’s see some of them further

State Manager

To deal with the states creation, AngularJS UI-Router provides the $stateProvider component. It works similar to AngularJS $routeProvider, but it is focused on states.

  • A state corresponds to a part of  the application in terms of the UI and navigation.
  • A state describes (using the controller, template and view properties) the looks of that part if the UI and what is done there.
  • States usually have things in common, so state hierarchies are used to leverage these relationships (parent/child states are known as nested states)

Define states

To define states, we use the .config method (as we already knew), but instead of setting our routes on $routeProvider, we set our states on the $stateProvider


.config(function($stateProvider, $urlRouterProvider) {
 $stateProvider
   .state('home', {
     url: '/home',
     templateUrl: 'partials/home.html'
  })
});

The lines above create a state named home using the state configuration object provided. This object has similar options to the one used when creating routes using $routeProvider in ngRoute module.

When an URL is specified in the state configuration object, this state is bound to an URL. When a state is activated, its template is inserted into the HTML tag containing the ui-view attribute of its parent state’s template.

When a state is activated, its templates are automatically inserted into the ui-view of its parent state’s template. If the state is a top-level state (it does not have a parent state), then its parent template is the index.html of the application.

<!-- index.html -->
<body ng-controller="MainController">
<section ui-view></section>
</body>

Templates

As we have already seen, templates are configured in the state configuration object when creating a state. There are different ways to define the template for a specific state:templates on each

  • template: A string of HTML content or a function that returns HTML
  • templateUrl: A string containing the URL path to a template or a function that returns a URL path string
  • templateProvider: A function that returns an HTML content string. This function can be injected and has access to locals

Controllers

We can assign a controller to the template of a state. Just like in ngRoute, we can either associate an already registered controller with a URL using the string function name or we can create a controller function that operates as the controller for the state.

It should be noted that if there is no template defined, then the controller will not be created.

The controllers are specified using the controller key of the state configuration object and there are different ways to do this:

/* Using function to create the controller*/
$stateProvider.state('home', {
  template: '<h1>{{welcome}}</h1>',
  controller: function($scope){
    $scope.welcome = 'Hello World';
  }
})

/* Using controller name if you already have a controller defined on the module */
$stateProvider.state('home', {
  template: ...,
  controller: 'HomeController'
})

/* Using controller as syntax */
$stateProvider.state('home', {
  template: '<h1>{{home.welcome}}</h1>',
  controller: function(){
    this.welcome = 'Hello World';
  },
  controllerAs: 'home'
})

If you need to resolve a list of dependencies which have to be injected to the controller before changing to the state, the resolve key of the state configuration object must be used. Check this to know more about this functionality.

State parameters

Like routes in ngRoute, When defining the URL of our states we can specify some placeholders in the URL and they will be converted as parameters which can be accessed in the controller through the $stateParams object:

$stateProvider
  .state('orders', {
    url: '/order/:orderId',
    template: '<div><h1>Welcome to your order history</h1>
               <div>Showing order id {{orderId}}</div>
               </div>',
    controller: function($scope, $stateParams) {
      $scope.orderId = $stateParams.orderId;
    }
  })

We can also use curly braces to specify parameters in the URL: /order/{orderId}.

Take into account that in state controllers, the $stateParams object will only contain the parameters that were registered with that state. So we will not see the parameters registered on other states, including ancestors. To avoid that, we should create a resolve in the parent state to add the parent state parameter in order to be available in child states. Check this to see how to do it.

Activating states

There are three main ways to activate a state of our application:

  • Navigate to the url associated with the state
  • Click a link containing the ui-sref directive (we will learn more about this later)
  • Using $state.go method

Nested states

To leverage all the power provided by the Ui-Route library, we will have to use undoubtedly the nested states. States can be nested within each other which means that the child state will be rendered inside the ui-view of the parent state. Child states, in turn, can have nested states and so on.

In order to define child states, we can use the dot notation in the state name when defining a new state with $stateProvider:

$stateProvider
  .state('home', {
    url: '/home'
    template: '<div>Home</div><div ui-view></div>'
  })
  .state('home.main', {
    url: '/main',
    template: '<div>Main News</div>'
  });
  .state('home.news', {
    url: 'news',
    template: '<div>List of News</div><ul><li ng-repeat="new in news"><a>{{new.title}}</a></li></ul>',
    controller: function($scope){
      $scope.news = [{ title: 'First News' }, { title: 'Second News' }];
    }
  });

The example above creates a “home” state bound to the “/home” URL and two child states, “main” bound to “/home/main” URL and “news” bound to “/home/news” URL. When activating one of the child states, the specific template will be rendered inside the ui-view element of the parent (home) state.

Child states can inherit some of the information contained in the parent state like resolved dependencies and custom state data properties.

Abstract states

There are situations where we need to have some common information available in several states. For this purpose UI-Router provides the possibility to specify abstract states.
Abstract states can have child states but they can not be activated itself neither transitioned to. An abstract state is implicitly activated when one of its child states are activated.
This is useful when:

  • we need to prepend a url to all the child state urls
  • we need to insert a template with its own ui-view that the child states will fill
  • we need to provide resolved dependencies (via resolve) in order to be used by child states
  • we need to provide inherited custom state data in order to be used by child states or events

Abstract states are defined specifying the abstract key in the state configuration object set to true.

$stateProvider
    .state('home', {
        abstract: true,
        templateURL: 'home.html'
    })

Multiple named views

Besides to provide parent/child relationship in states, UI-Router gives us the capability to have multiple named views inside states which will not be bound to any URL.

Having multiple views in our application can be very powerful. For example, maybe you have a sidebar on your site that has things like tweets, recommended items, advertising banner, popular posts, recent posts, users or whatever. These different blocks can all be separated and injected into our template. Each view will have its own controller and template, so our application will be better organized and cleaner.

This feature allows us having our application modular and also lets us reuse data in different templates to reduce the amount of duplicate code.

In order to specify named views in states, we have to make use of the views property of state configuration object.
For example, if we have a view like this:

<!-- partial-aside.html -->
<div>
  <div ui-view="tweets"></div>
  <div ui-view="recommendations"></div>
  <div ui-view="users"></div>
</div>

We can create named views to fill each of these templates:

$stateProvider
  .state('aside', {
    views: {
      // the main template will be placed here (relatively named). This template will be placed inside the ui-view of the parent state
      '': { templateUrl: 'partial-aside.html' }, 
      'tweets': { ... templates and/or controllers ... },
      'recommendations': {},
      'users': {},
    }
  })

As stated in the official documentation, if we set the views parameter, then the state’s templateUrl, template, and templateProvider will be ignored. If we want to include a parent template in our routes, we’ll need to define an abstract state that contains a template, and a child state under the layout state that contains the ‘views’ object

Relative vs Absolute Naming

When using multiple views, UI-Router assigns every view to an absolute name. The structure for this is viewName@stateName, where viewname is the name used in the view directive and state name is the state’s absolute name, e.g. aside. You can also choose to write your view names in the absolute syntax. Remember that if an @ is used then the view path is considered absolute.

The next example has been taken from the documentation and it explains really well how to define relative and absolute names and how they are resolved:

$stateProvider
  .state('contacts', {
    // This will get automatically plugged into the unnamed ui-view 
    // of the parent state template. Since this is a top level state, 
    // its parent state template is index.html.
    templateUrl: 'contacts.html'   
  })
  .state('contacts.detail', {
    views: {
        ////////////////////////////////////
        // Relative Targeting             //
        // Targets parent state ui-view's //
        ////////////////////////////////////

        // Relatively targets the 'detail' view in this state's parent state, 'contacts'.
        // <div ui-view='detail'/> within contacts.html
        "detail" : { },            

        // Relatively targets the unnamed view in this state's parent state, 'contacts'.
        // <div ui-view/> within contacts.html
        "" : { }, 

        ///////////////////////////////////////////////////////
        // Absolute Targeting using '@'                      //
        // Targets any view within this state or an ancestor //
        ///////////////////////////////////////////////////////

        // Absolutely targets the 'info' view in this state, 'contacts.detail'.
        // <div ui-view='info'/> within contacts.detail.html
        "info@contacts.detail" : { }

        // Absolutely targets the 'detail' view in the 'contacts' state.
        // <div ui-view='detail'/> within contacts.html
        "detail@contacts" : { }

        // Absolutely targets the unnamed view in parent 'contacts' state.
        // <div ui-view/> within contacts.html
        "@contacts" : { }

        // absolutely targets the 'status' view in root unnamed state.
        // <div ui-view='status'/> within index.html
        "status@" : { }

        // absolutely targets the unnamed view in root unnamed state.
        // <div ui-view/> within index.html
        "@" : { } 
  });

$state

$state service is responsible for representing states as well as transitioning between them. It also provides interfaces to ask for current state or even states you’re coming from.
To see all the properties and methods of $state, check the documentation.

Link to other states using ui-sref

When creating a link with UI-Router, we will use ui-sref directive. The href will be generated from this and we can link to a certain state of our application.
For example:

<!-- More code of a template -->
<a ui-sref=".main">Main News</a>
<a ui-sref=".news">News list</a>
<!-- More code of a template

This will create links linking respectively to main and news child states of the current state. That means both states are nested states.

Conclusion

This has been an overview of the main features provided by this great tool. As we have seen, AngularJS UI-Router gives us many features beyond the one provided by ngRoute to create awesome Angular-based applications which are modular, extensible and well-structured.

In future entries we will cover some other aspects and components not covered in this post like events used, regex parameters, etc.

References

AngularJS UI-Router wiki
AngularJS UI-Router Documentation

Antonio Morales

Antonio has graduated from Computer Engineering in the University of Seville. He also holds a Master degree in Engineering and Software Technology from the University of Seville as well. During his working life he has been involved in a large number of projects both national and European projects from Digital Identity and Security to Semantic Search, NLP and Machine Learning. He is also committer in the Apache Software Foundation in the Apache Stanbol project and collaborator of other Apache projects. Mainly involved in projects based on Java and Semantic technologies, he loves all the new technologies both front-end and back-end side, always eager to learn new technologies to be applied to new projects.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Thai
Thai
9 years ago

I’m waiting for part 3.

Back to top button