Best practice to store data loaded from a server

Hello everybody,
I am trying to understand the best way to load data from a server into a service. Example:

.factory( "Init", function( $http, SERVER ){
  return {
  	get: function(){
  		return $http.get( SERVER.url + '/init/');
  	}
  }
})

.controller('AppCtrl', function($scope, Init) {
  Init.get().then( function(data){
    console.log(data);
  });
})

but in this way everytime a controller needs the data a new query is done.
Also using the service instead of the factory nothing change

.service( "Init", function( $http, SERVER ){
  var promise = $http.get( SERVER.url + '/init/' )
    .success(function(data){
      return data;
  });
  return promise;
})

.controller('AppCtrl', function($scope, Init) {  
  Init.then( function(data){
    console.log(data);
  });
})

I think we have the same problem of one query for every controller, right?

Another way is to resolve from the state, like this:

$stateProvider
  .state('app', {
    url: '/app',
    abstract: true,
    templateUrl: 'templates/menu.html',
    resolve: {
      myData: function (Init){
        // this method is from the factory above
        return Init.get();
      }
    },
    controller: 'AppCtrl'
  })

and then in the controller

.controller('AppCtrl', function($scope, myData) {
  console.log(myData);
})

in this last example all other controllers that are in a child state of app will inherit myData.
But I would like to know how to set the data inside the service and not to the app state, like is now.

This is a possible solution, that works, but I don’t know if is a best practice or if I am trying to rebuild the wheel.

In the state I make the request, then use the factory method to set the object inside the service and finally resolve.

.state('app', {
    url: '/app',
    abstract: true,
    templateUrl: 'templates/menu.html',
    resolve: {
      myData: function($http, Init){
        return $http({ method:'GET', url: SERVER.url + '/init/'})
        .then(function(data){
          Init.set(data);
          return data;
        });
      }
    },
    controller: 'AppCtrl'
  })

.factory( "Init", function( $http, SERVER ){
  var settings = {};
  return {
  	set: function(data){
  		settings = data;
  	},
  	get: function(){
  		return settings;
  	}
  }
});

And from the controller I can access it in this way:

.controller('OtherCtrl', function($scope, Init) {
  console.log(Init.get());
})

Anyway using the resolve method we can encounter in a possible user experience issue if there’s some problem with the connection.

I feel lost! :smile:
thanks everybody for support!

Maybe you can load a default set of data from local, then update with your server if the connection will be available.

Hello Fabio,
thanks for answer.
I mean, the application to run needs the connection. I was wondering about slow connection during the startup, because I don’t know if is possible to show the loader in the state, so the risk is the leave the user without feedback.

Ok, not so simple… so my hints are:

  • if possible use a cache, like DSCacheFactory
  • provide a connection error message with automatic or manual retry
    Unfortunately there isn’t many options…

yes you can do that.
you have to use a $rootScope variable and depends on his value you can render a loader over the ng-view directive.
You can set this variable to true when you handle a xhr request and when you received the response you set it to false and the loader will be hidden, this is the simpliest method i have implemented.

<div class='col-lg-12' ng-if="isRouteLoading"><h1>Caricamento... <i class="fa fa-cog fa-spin"></i></h1></div>
<div ng-if="!isRouteLoading" ui-view></div>

Then you set it to true on $stateChangeStart event and to false on $stateChangeSuccess event, anyway you can display loader even if you perform xhr request from controller and set it to true :wink:

Hello Ebreo,
sorry for the delay and thanks for the reply.
Where do I set the $rootScope variable? app, services, controller?

And another doubt.
If I have a resolve obj inside the app state, the system wait to resolve before loading others states built on this abstract state? Example with another state on top:

.state('app.first', {
    url: '/first',
    views: {
      'menuContent': {
        templateUrl: 'templates/first.html',
        controller: 'FirstCtrl'
      }
    }
  })

the resolve is already resolved 100%?
Because from here, I mean from FirstCtrl, I can access data both with myData and Init.get().

thanks a lot for support.

        $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState) {
            if (!AuthService.isLogged()) {
                event.preventDefault();
                var isRefreshed = AuthService.getNewToken();
                AuthService.handleNewToken(isRefreshed, fromState);
            }
            $rootScope.isRouteLoading = true;
        });

        $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
            $timeout(function () {
                $rootScope.isRouteLoading = false;
            }, 300);
            $rootScope.previousState = from.name;
        });

Can you explain better the second question?
Nevermind i think that if you have an abstract state with resolve method all childrens will waiting until all methods will be resolved, so i reccomend you to use resolve callback per state and not per abstract state.

Hello,
I am here again :smile:
May I ask why is not a good practice to resolve inside the abstract state? In this way all che childern state get the data and they are sure to not being instantiate before it. I’ve seen an example here.
Another place to resololve data initialization could it be the in the main module.run()? But in this case how to make the promise?
thanks for the patience!

Hi

I am having the exact same issue here.

Can you please share your final code ?

@fabioferrero, I also need to load a default set of local data, as you suggested, and then update it with my server, can you suggest the best approach for this ?

Pretty simple:

	function getUrl() {
		var notfirstrun= $localStorage.notfirstrun;
		var url= localurl;
		if (typeof notfirstrun !== 'undefined' && notfirstrun) {
			url= remoteurl;
		} else {
			$localStorage.notfirstrun= 1;
		}
		return url;
	}

then use:

var url= getUrl();

and
$http.get(url)

Finally, after many tests, in my opinion the best choice is to load the settings data from the abstract state and insert them into a factory available from everywhere. And then, as Fabio shown before, you can decide to store the settings inside the local Storage or not, it depends on your needs.

I read some others prefer to make a view, the first view to be loaded, dedicated to this job, but I am not agree because if you need to access the app from a different URL, that view, by default, it is not loaded. Instead the abstract url it is always the one loaded because “contain” the other views.

Bye!