javascriptangularjsdependencieslokijs

Issue calling service inside app.config


Having some problems right now calling a service I created that uses LokiJS (http://lokijs.org/#/) inside of my app.config.

I know that in Angular you are not really supposed to attempt to use services inside of the config section during app start but from some other answers pieced together on stack overflow I've been able to actually access the service, but am still running into issues that are now seeming to be LokiJS specific.

Below is a copy of my service:

(function(){

angular.module('LocalStorage', [])
  .service('localStorageService', [
    'Loki',
    '$q',
   function(Loki, $q) {

      var self = this;

      var db;
      var localstorage;

      self.initDB = function() {
           var adapter = new LokiCordovaFSAdapter({"prefix": "loki"});
           // Store to a file localstorage.json
           db = new Loki('localstorage.json',
                   {
                       autosave: true,
                       autosaveInterval: 1000, // 1 second
                       adapter: adapter
                   });

          // Testing purposes only
                   console.log('end of Init DB'); 
       };

      self.getLocalStorage = function() {

          console.log('calling get local storage');

          return $q(function (resolve, reject) {
              var options = {};

              console.log('inside the return');

              db.loadDatabase(options, function () {
                  console.log('right before call getcollection');
                  localstorage = db.getCollection('localstorage');

                  console.log('grabbed local storage collection');

                  if (!localstorage) {
                    console.log('localstorage did not exist so MAKING NEW ONE');
                    localstorage = db.addCollection('localstorage');

                  }

                  resolve(localstorage.data);
              });
          });
      };

        self.getFromLocalStorage = function(value) {

          var returnValue = localstorage.findOne({key: value});

          return returnValue;
        };

        self.addToLocalStorage = function(key, value) {
          // Format record to be stored
          var data = {'key': key, 'value': value};

          // Check if this 'key' already exists in localstorage
          if (self.getFromLocalStorage(key)) {
            console.log('ATTEMPTING TO UPDATE INSTEAD OF INSERT');
            // If it exists then run an 'update'
            //var lokiValue = self.getFromLocalStorage(key)['$loki'];
            var lokiValue = self.getFromLocalStorage(key);

            // Replace with new value
            lokiValue['value'] = value;

            // Now update
            localstorage.update(lokiValue);
          } else {
            console.log('ATTEMPTING TO INSERT INSTEAD OF UPDATE');
            // If key does not exist, insert
            localstorage.insert(data);
          }
      };

       self.deleteFromLocalStorage = function(value) {

          // Grab object that you are trying to delete
          var obj = self.getFromLocalStorage(value);

          localstorage.remove(obj);
      };

      // TODO Should only be used for testing purposes
      self.deleteDatabase = function() {

        console.log('ATTEMPTING TO DELETE DATABASE');
        db.deleteDatabase();
      };

    }]);
  })();

And the app.config:

app.config([
    '$stateProvider',
    '$httpProvider',
    'jwtInterceptorProvider',
    'localStorageServiceProvider',
    function ($stateProvider, $httpProvider, jwtInterceptorProvider, localStorageServiceProvider) {

        localStorageServiceProvider.$get().initDB();

        localStorageServiceProvider.$get().getLocalStorage()
       .then(
        function (result) {
                 // Auth interception
                 console.log("INFO", 'Create auth interceptor');
                 jwtInterceptorProvider.tokenGetter = function (jwtHelper) {
                     var token = localStorageServiceProvider.$get().getFromLocalStorage('token');
                     return token;
                 };
                 $httpProvider.interceptors.push('jwtInterceptor');
                   },
           function (error) {
               // handle errors here
               console.log('Something crazy happened with grabbing collection, This should not happen.');
            }
         );

    }]);

So basically what is happening is that the code never executes past the "console.log('inside the return');" logging statement inside of the getLocalStorage() function inside the localStorageService. I never see any of the logs past that come up so it seems as if "db.loadDatabase()" is never actually even being run.

And if that is the case I am confused as to how the "initDB()" function is even properly executed before getLocalStorage() is called.

I am not sure if this is because of some Loki dependency issue where it is not properly able to be accessed because I am attempting to call this function inside of the app.config, or something else entirely.

The reason I am even trying to run these functions from this service inside of app.config is because I want to be able to store the token that is being accessed via the JwtInterceptorProvider.

Any help with this would be greatly appreciated, I am pretty new to both Angular as well as Loki so I am sure there is just a small issue here that I'm missing, or a way in which I have this structure setup that is just not proper. Thank you!


Solution

  • Services shouldn't be instantiated with $get(), they aren't available in config for natural reasons, this just breaks the things. They are no longer singletons with that (besides the fact that this makes them untestable).

    This

        localStorageServiceProvider.$get().initDB();
    

    and this

        localStorageServiceProvider.$get().getLocalStorage()
    

    refer to two different instances of localStorageService.

    There are several race conditions here. The main problem here is that localStorageService is asynchronous. No matter if it was called in config or run phase, at the time when the app has reached run phase and $http was instantiated and started to make requests, this piece of code

                 jwtInterceptorProvider.tokenGetter = ...;
                 $httpProvider.interceptors.push('jwtInterceptor');
    

    may still not be executed.

    There is no way to 'pause' Angular app initialization to resolve application-wide dependencies.

    The first way to deal with this is to use router resolver, it should be inherited from root state to make sure that it will surely fire on every state.

    Some providers can be configured after service instantiation. It may be useful if they should be configured after bootstrapping the app. This voids the warranty, there's no guarantee that hacking services like that won't cause side effects.

    app.config(($provide, $httpProvider, jwtInterceptorProvider, $stateProvider) => {
      $stateProvider.state('root', {
        abstract: true,
        resolve: { init, 'initResolver' }
      });
      ...
    
      // don't even needed if initResolver is defined as a function in config
      $provide.value($httpProvider);
      $provide.value(jwtInterceptorProvider);
    });
    
    app.factory('initResolver', (localStorageService, $httpProvider, jwtInterceptorProvider) => {
      localStorageService.getLocalStorage()...
      jwtInterceptorProvider.tokenGetter = ...;
      $httpProvider.interceptors.push('jwtInterceptor');
    });
    

    The second way is to postpone bootstrapping process. This may be a good choice if app dependencies are non-Angular, like in this case. Since Loki database is supposed to be reused by the app itself, it is beneficial to refactor localStorageService to just use the existing object (it may not even need to wrap its methods)

    var lsDb = new Loki(...);
    var ls;
    
    lsDb.loadDatabase(..., () => {
      ls = lsDb.addCollection('localstorage');
    
      angular.module('app')
      .constant('localStorageDb', localStorageDb);
      .factory('localStorageService', () => {
        return ls;
      });
    
      angular.bootstrap(document, ['app']);
    })