AngularJS ui-router: how to resolve typical data globally for all routes?

Sray picture Sray · Jan 30, 2015 · Viewed 11.1k times · Source

I have an AngularJS service which communicates with the server and returns translations of different sections of the application:

angular
     .module('utils')
     .service('Translations', ['$q','$http',function($q, $http) {
        translationsService = {
            get: function(section) {
                if (!promise) {
                    var q = $q.defer();
                    promise = $http
                            .get(
                                '/api/translations',
                                {
                                    section: section
                                })
                            .success(function(data,status,headers,config) {
                                q.resolve(result.data);
                            })
                            .error(function(data,status,headers,config){ 
                                q.reject(status);
                            });
                    return q.promise;
                }
            }
        };

        return translationsService;
    }]);

The name of the section is passed as the section parameter of the get function.

I'm using AngularJS ui-router module and following design pattern described here

So I have the following states config:

angular.module('app')
    .config(['$stateProvider', function($stateProvider) {
    $stateProvider
    .state('users', {
        url: '/users',
        resolve: {
            translations: ['Translations',
                function(Translations) {
                    return Translations.get('users');
                }
            ]            
        },
        templateUrl: '/app/users/list.html',
        controller: 'usersController',
        controllerAs: 'vm'
    })
    .state('shifts', {
        url: '/shifts',
        resolve: {
            translations: ['Translations',
                function(Translations) {
                    return Translations.get('shifts');
                }
            ]            
        },
        templateUrl: '/app/shifts/list.html',
        controller: 'shiftsController',
        controllerAs: 'vm'
    })

This works fine but as you may notice I have to explicitly specify translations in the resolve parameter. I think that's not good enough as this duplicates the logic.

Is there any way to resolve translations globally and avoid the code duplicates. I mean some kind of middleware.

I was thinking about listening for the $stateChangeStart, then get translations specific to the new state and bind them to controllers, but I have not found the way to do it.

Any advice will be appreciated greatly.

Important note: In my case the resolved translations object must contain the translations data, not service/factory/whatever.

Kind regards.

Answer

Radim Köhler picture Radim Köhler · Jan 30, 2015

Let me show you my approach. There is a working plunker

Let's have a translation.json like this:

{
  "home" : "trans for home",
  "parent" : "trans for parent",
  "parent.child" : "trans for child"
}

Now, let's introduce the super parent state root

$stateProvider
  .state('root', {
    abstract: true,
    template: '<div ui-view=""></div>',
    resolve: ['Translations'
      , function(Translations){return Translations.loadAll();}]
  });

This super root state is not having any url (not effecting any child url). Now, we will silently inject that into every state:

$stateProvider
  .state('home', {
      parent: 'root',
      url: "/home",
      templateUrl: 'tpl.html',
  })
  .state('parent', {
      parent: 'root',
      url: "/parent",
      templateUrl: 'tpl.html',
  })

As we can see, we use setting parent - and do not effect/extend the original state name.

The root state is loading the translations at one shot via new method loadAll():

.service('Translations', ['$http'
    ,function($http) {
    translationsService = {
      data : {},
      loadAll : function(){
        return $http
          .get("translations.json")  
          .then(function(response){
            this.data = response.data;    
            return this.data;
          })
      },
      get: function(section) {
        return data[section];
      }
    };

    return translationsService;
}])

We do not need $q at all. Our super root state just resolves that once... via $http and loadAll() method. All these are now loaded, and we can even place that service into $rootScope:

.run(['$rootScope', '$state', '$stateParams', 'Translations',
  function ($rootScope, $state, $stateParams, Translations) {
    $rootScope.$state = $state;
    $rootScope.$stateParams = $stateParams;
    $rootScope.Translations = Translations;
}])

And we can access it anyhwere like this:

<h5>Translation</h5>
<pre>{{Translations.get($state.current.name) | json}}</pre>

Wow... that is solution profiting almost from each feature coming with UI-Router... I'd say. All loaded once. All inherited because of $rootScope and view inheritance... all available in any child state...

Check that all here.