Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 864
Sray Avatar asked Jan 30 '15 14:01

Sray


2 Answers

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.

like image 76
Radim Köhler Avatar answered Oct 13 '22 18:10

Radim Köhler


Though this is a very old question, I'd like to post solution which I'm using now. Hope it will help somebody in the future. After using some different approaches I came up with a beautiful angularjs pattern by John Papa

He suggest using a special service routerHelperProvider and configure states as a regular JS object. I'm not going to copy-paste the entire provider here. See the link above for details. But I'm going to show how I solved my problem by the means of that service.

Here is the part of code of that provider which takes the JS object and transforms it to the states configuration:

function configureStates(states, otherwisePath) {
    states.forEach(function(state) {
        $stateProvider.state(state.state, state.config);
});

I transformed it as follows:

function configureStates(states, otherwisePath) {

    states.forEach(function(state) {

        var resolveAlways = {

            translations: ['Translations', function(Translations) {

                if (state.translationCategory) {

                    return Translations.get(state.translationCategory);

                } else {

                    return {};

                }

            }],

        };  



        state.config.resolve =

            angular.extend(state.config.resolve || {}, resolveAlways || {});



        $stateProvider.state(state.state, state.config);

    }); 

}); 

And my route configuration object now looks as follows:

        {
            state: ‘users’,
            translationsCategory: ‘users’,
            config: {
                controller: ‘usersController’
                controllerAs: ‘vm’,
                url: ‘/users’.
                templateUrl: ‘users.html'
            }

So what I did:

I implemented the resolveAlways object which takes the custom translationsCategory property, injects the Translations service and resolves the necessary data. Now no need to do it everytime.

like image 20
Sray Avatar answered Oct 13 '22 18:10

Sray