Angular Promises and Advanced Routing

What is a promise?

A promise represents the result of an asynchronous operation

What is a promise?

  • A promise represents a task that will finish in the future;
  • A promise has three methods:
    • then()
    • catch()
    • finally()
  • A promise will be resolved or rejected

Async Example

var a = 10;

window.setTimeout(function(){
    a += 10;
}, 1000);

console.log(a); // Will output 10;

Js async operations

  • setTimeout() / setInterval();
  • XmlHttpRequest;
  • Element Creation;
  • Web Workers;
  • Generators;

A promise can be in one of three different states

* Immutable

Promises and $q

Angular Promises

Angular uses promises for everything that is async:

  • $timeout;
  • $http & response interceptors;
  • $resource;
  • $routeProvider.when();
  • much more.

AngularJs Deferred API

Implemented using $q service

function createPromise(resolve){
    defered = $q.defer();

    window.setTimeout(function(){
        if (resolve) defered.resolve(10);
        else defered.reject("You provided a false value");
    }, 1000);

    return defered.promise;
}

var promise = createPromise(true);
promise.then(function(value){
    console.log(value);
});

console.log("The value will be shown after this.");

Angular promise API

Result of deffered.promise

var promise = createPromise(true);

promise
    .then(function(value){
        console.log(value)  //10
        return value + 10;
    }, errorFn, notifyFn)

    .then(function(value){
        console.log(value)  //20
        return $q.reject(20);
    }, errorFn, notifyFn)

    .catch(errorFn)

    .finally(callbackFn);

Angular uiRouter

Advanced routing

ngRoute - single level

uiRouter - multiple levels

States

  • A place within an app
  • Nested hierarchy
  • Names are names
  • Navigate by name or url
  • Multiple views (ui-view)
  • Populate any view
  • State populates 'parts'

Routes

  • A url within your app
  • Flat hierarchy
  • Names are urls
  • Navigate by url only
  • Single view (ng-view)
  • Populate the one view
  • Directives  populate 'parts'

uiRouter vs. ngRoute

Simple Example

<body>
    <ul>
        <li><a ui-sref="home">Home</a></li>
        <li><a ui-sref="about">About</a></li>
    </ul>    

    <section ui-view></section>
</body>
angular
    .module("myApp", [])
    .config(['$stateProvider', function($stateProvider){
        $stateProvider
            .state('home', {
                template: '<h1>Home</h1>'
            })
            .state('about', {
                template: '<h1>About</h1>'
            });
    }])

Config Object

$stateProvider
    .state('stateName', {
        template: 'String Html content',
        templateUrl: 'String URL',
        templateProvider: function(){}, // Function, must return HTML String
        controller: 'Function or name as String'
        controllerProvider: function(){}, // Function, must return controller function or name as String
        resolve: {} // A map of dependencies wich shoul be resolved and injected into controller
        url: 'A url with optional params',
        params: [], //Array of parameter names or regular expressions
        views: {
            //Object to define multiple views
            'nameOfView': {
                template: '',
                controller: ''
            }
        }, 
        abstract: true //An abstract state will never be directly activated, but can provide inherited properties to its common children states.
        data: {},
        onEnter: function(){},
        onExit: function(){},
        reloadOnSearch: true
    })

Resolve Property

The resolve property is a map object:

  • key {string} : name of the dependency
  • factory {string|function} : 
    • string: alias for a service
    • factory:
      • Return value is treated as the dependency.
      • If the result is a promise, it's resolved before the controller is instatiated.

Resolve Example

$stateProvider
    .state('stateName', {
        resolve:{
            simpleObj: function() { 
                return {value: 'some string'}
            },

            promiseObj: ['$http', function($http) { 
                //$http returns a promise
                return $http({method: 'GET', url: '/someUrl'});
            }]
        },
        controller: ['$scope', 'simpleObj', 'promiseObj', function($scope, simpleObj, promiseObj){
            $scope.simple = simpleObj.value;

            // You can be sure that promiseObj is ready to use!
            $scope.items = promiseObj.items;
            $scope.items = promiseObj2.items;
        }]
    })

Activating a state

There are three main ways to activate a state:

  1. Call $state.go();
  2. Click a link containing the ui-sref directive;
  3. Navigate to the url associated with the state.

$state.go()

myApp.controller('contactCtrl', ['$scope', '$state', 
  function($scope, $state){
     $scope.goToDetails = function(){
        $state.go('contact.details', {id: selectedId});
     }
  } 

Params:

  1. state name
  2. state params (optional)
  3. options (optional)

{ location: true, inherit: true, relative: $state.$current, notify: true, reload: false }

Relative Navigation

  1. ^ is up;
  2. . is down.

You can navigate relative to current state 
by using special characters:

Example

Go to sibling - $state.go('^.1')

ui-sref

Generate anchors for state references

<a ui-sref="contacts.detail({ id: 3 })"></a>

<!-- Can Generate -->
<a ui-sref="contacts.detail({ id: 3 })" href="#/contacts/3"></a>

Can use relative paths and can have options

<a ui-sref="^" ui-sref-opts="{location : false}">Go up</a>

The path is relative to the state that the link lives in.
In other words the state that loaded the template containing the link.

Nested states

$stateProvider
    .state("home", { ... });
    .state("contacts", { ... });
    .state("contacts.detail", { ... });
    .state("contacts.detail.edit", { ... });

The dot in the state names auto-denotes parental hierarchy.
It knows that 'contacts.detail' is a child of 'contacts'.

Nested States

<!-- index.html -->
<body>
  <div ui-view></div>
</body>

<!-- page.html -->
<h1>My Contacts</h1>
<div ui-view></div>

<!-- page.list.html -->
<ul>
  <li ng-repeat="item in list">
    <a>{{item.name}}</a>
  </li>
</ul>
angular
    .module('myapp', ["ui.router"])
    .config(function($stateProvider, $urlRouterProvider){
      $urlRouterProvider.otherwise("/list")
      
      $stateProvider
      .state('page', {
        abstract: true, 
        templateUrl: 'page.html',
        resolve: {
            list: function($http){
                return $http({method: 'GET', url: '/someUrl'}); 
            }
        }
      })
      .state('page.list', {
        url: '/list',
        controller: function($scope, list){
          $scope.list= list;
        }
        templateUrl: 'page.list.html'
      });
    });

Inheritance

Scope inherits from parent

  • properties
  • methods

* This is how angular behaves, not a feature

** Template nesting must reflect state nesting

 

Child states inherit from their parents:

  • resolved dependencies
  • custom data

Inherited Resolved Dependencies

.state('parent', {
      resolve:{
         resA:  function(){
            return {'value': 'A'};
         }
      },
      controller: function($scope, resA){
          $scope.resA = resA.value;
      }
   })
   .state('parent.child', {
      resolve:{
         resB: function(resA){
            return {'value': resA.value + 'B'};
         }
      },
      controller: function($scope, resA, resB){
          $scope.resA2 = resA.value;
          $scope.resB = resB.value;
      } 

Multiple named views

<body>
  <div ui-view="filters"></div>
  <div ui-view="tabledata"></div>
  <div ui-view="graph"></div>
</body>
$stateProvider
  .state('report',{
    url: '',
    resolve: {},
    views: {
      'filters': {
        templateUrl: 'report-filters.html',
        controller: function($scope){}
      },
      'tabledata': {
        templateUrl: 'report-table.html',
        controller: function($scope){}
      },
      'graph': {
        templateUrl: 'report-graph.html',
        controller: function($scope){}
      },
    }
  })

View Name

Can use relative or absolute naming

Relative (always parent)
'filters' - 'filters' view in parent template
' ' - unnamed view in parent template

 

Absolute (uses @ symbol)
'filters@report' - 'filters' view in 'report' state's template
'filters@' - 'filters' view in index.html.
'@report' - unnamed view in 'report' state's template

Url routing

$stateProvider
    .state('page', {
        url: "/page",
        templateUrl: 'page.html'
    })
    .state('page.item', {
        url: "/{id}?filter&anotherParam",
        templateUrl: 'page-item.html',
        controller: function ($stateParams) {
            console.log($stateParams.id)
        }
    })
    .state('page.item.details', {
        url: "/details",
        templateUrl: 'page-item-details.html',
    })

page.item's url becomes:

/page/{id}?optionalParams

page.item.details url becomes:

/page/{id}/details?optionalParams

Demo App

Credits

Angular Promises and Advanced Routing

By Alexe Bogdan

Angular Promises and Advanced Routing

Angular Framework: $q and uiRouter

  • 1,499