0

I'm working with an Angular directive which loads JSON data from localStorage.

This was easily done with localStorage.getItem(id), but now I am trying to make it work via an API call (which in turn pulls from a database).

I have the Angular factory code working, and the http requests working, but in the directive code there's a _handleAsyncLoad() function which is throwing me. In other words, I'm trying to use the built-in promise for the serialized object coming back from the API layer.

ex/ I've written the new anonymous function, _getItemFromAPI:, but unsure if I need to use the _handleAsyncLoad: function. If not, what's the best way to ensure my serialized object is populated with data prior to returning it.

angular.module('ui.dashboard')
  .factory('DashboardState', ['$log', '$q', 'dashboardcontext', '$rootScope', function ($log, $q, dashboardcontext, $rootScope) {
      function DashboardState(storage, id, hash, widgetDefinitions, stringify) {
          this.storage = storage;
          this.id = id;
          this.hash = hash;
          this.widgetDefinitions = widgetDefinitions;
          this.stringify = stringify;
      }

DashboardState.prototype = {
        
load: function (dashboardId) {

      if (!this.storage) {
	  return null;
      }

      var serialized;

      // fetch dashboard layout from storage
      if (dashboardId != null && dashboardId != undefined) {
	  //serialized = this.storage.getItem(dashboardId);	// OLDER, SIMPLER WAY 
	  
	  serialized = this._getItemFromAPI($rootScope);	// NEW WAY, PULL DATA VIA API !	  
      }
      else {
	  // revert to original line; see dashboardOptions to main-controller
	  serialized = this.storage.getItem(this.id);
      }

      if (serialized) {
	  // check for promise
	  if (angular.isObject(serialized)) {  
	      return this._handleAsyncLoad(serialized);
	  }
	  // otherwise handle synchronous load
	  return this._handleSyncLoad(serialized);
      } else {
	  return null;
      }
  },         

  _getItemFromAPI: function ($rootscope) {
      // SERVER-SIDE API CALL TO PERSIST DASHBOARD TO STORAGE - 09/03/2015 BM:
      var sid = $rootScope.rageSessionVars.sessionID;
      var userid = $rootScope.rageSessionVars.userID;
      var dashboardId = this.id;

      dashboardcontext.getDashboardImage(sid, userid, dashboardId).then(function (data) {
	  if (data.status == "FAIL") {
	      window.alert("Failed to retrieve dashboard. " + data.messages);
	      return false;
	  }
	  else {                      
	      return data;
	  }
      });
      return new Promise(function (resolve, reject) { });
  },

  _handleSyncLoad: function (serialized) {

      var deserialized, result = [];

      if (!serialized) {
	  return null;
      }

      if (this.stringify) {
	  try { // to deserialize the string

	      deserialized = JSON.parse(serialized);

	  } catch (e) {

	      // bad JSON, log a warning and return
	      $log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized);
	      return null;

	  }
      }
      else {
	  deserialized = serialized;
      }     

      // Cache widgets
      var savedWidgetDefs = deserialized.widgets;      

      return result;
  },

  _handleAsyncLoad: function (promise) {
      var self = this;
      var deferred = $q.defer();
      promise.then(
	// success
	function (res) {
	    var result = self._handleSyncLoad(res);
	    if (result) {
		deferred.resolve(result);
	    } else {
		deferred.reject(result);
	    }
	},
	// failure
	function (res) {
	    deferred.reject(res);
	}
      );

      return deferred.promise;
  }
 };
      
 return DashboardState;
 }]);

databoardcontext factory code:

function getDashboardImage(sid, userid, id) {
    var rageVars = $rootScope.rageSessionVars;
    var url = "http://" + rageVars.domainName + ":" + rageVars.port + "/api/dashboards";
    var sid = rageVars.sessionID;
    var apiCall = "getDashboardImage";
    var cssClass = "html";
    var req = {
	method: 'POST',
	url: url,
	headers: {
	    'Content-Type': 'application/json', // application/x-www-form-urlencoded
	},
	data: { sid: sid, apiCall: apiCall, userid: userid, id: id }
    };
    var deferred = $q.defer();
    deferred.notify("Retrieving dashboard image...");

    $http(req).success(function (data, status, headers, config) {

	deferred.resolve(data);

    }).error(function (data, status, headers, config) {

	console.log('Error retrieving dashboard ');
	deferred.resolve();
    });

    return deferred.promise;
}

******** UPDATE Sept 8, 2015 2:55pm: Thanks to the gent who provide the answer, I'm posting some updated code to show what is now working. ********

angular.module('ui.dashboard')
.factory('DashboardState', ['$log', '$q', 'dashboardcontext', '$rootScope', function ($log, $q, dashboardcontext, $rootScope) {

var that = this;       // *** CREATED NEW OBJECT HERE. REASSIGN BELOW IN load: FUNCTION ***

function DashboardState(storage, id, hash, widgetDefinitions, stringify) {
  this.storage = storage;
  this.id = id;
  this.hash = hash;
  this.widgetDefinitions = widgetDefinitions;
  this.stringify = stringify;
}

DashboardState.prototype = {
  save: function (widgets) {
     /// SAVE CODE OMITTED FOR BREVITY
  },
  
  load: function (dashboardId) {
      
      var useLocalStorage = false;     // retrieve from localStorage or via API layer - 09/04/2015 BM:

      var serialized;

      if (useLocalStorage) {           // retrieve dashboard layout from localStorage
	  if (!this.storage) {
	      return null;
	  }
	  if (dashboardId != null && dashboardId != undefined) {
	      serialized = this.storage.getItem(dashboardId);

	      // save the current dashboard id for next load
	      this.storage.setItem("defaultDashboardId", dashboardId);
	  }
      }
      else {	  
	  
	  if (dashboardId != null && dashboardId != undefined) {

	      this.storage.setItem("defaultDashboardId", dashboardId);
	      
	      that = this;	// **** VERY IMPORTANT TO REASSIGN ***
	      
// *** RETURN IS VERY IMPORTANT HERE, AS WELL AS THE then() SECTION ***
	      return this._getItemFromAPI($rootScope).then(function (data) {                           		 
      		  
		  return that._handleSyncLoad(data, true);  // *** that. IS NOW AVAILABLE ON THE SCOPE ***

	       });                      
	  }
	  else {
	      // revert to original line; see dashboardOptions to main-controller
	      serialized = this.storage.getItem(this.id);
	  }                  
      }

      if (serialized) {
	  // check for promise
	 if (angular.isObject(serialized)) {    
	      return this._handleAsyncLoad(serialized);
	  }
	  // otherwise handle synchronous load
	  return this._handleSyncLoad(serialized);
      } else {
	  return null;
      }

  },         

  _getItemFromAPI: function ($rootscope) {
      // SERVER-SIDE API CALL TO PERSIST DASHBOARD TO STORAGE - 09/03/2015 BM:
      var sid = $rootScope.rageSessionVars.sessionID;
      var userid = $rootScope.rageSessionVars.userID;
      var dashboardId = this.id;

      return dashboardcontext.getDashboardImage(sid, userid, dashboardId).then(function (data) {
	  return data.data[0];
      });            
  },

  _handleSyncLoad: function (serialized, isParsed) {
      // @serialized {JSON} - parsed or unparsed Json object 
      // @isParsed {Boolean} - false if loaded from localStorage.getItem(); true if loaded from API, Json string already parsed.

      var deserialized, result = [];

      if (!serialized) {
	  return null;
      }

      if (isParsed) {    // JSON already deserialzed in load: above; see _getItemFromAPI().then data object - 09/04/2015 BM:

	deserialized = serialized;               

      }
      else {
	  if (this.stringify) {
	      try { // to deserialize the string

		  deserialized = JSON.parse(serialized);

	      } catch (e) {

		  // bad JSON, log a warning and return
		  $log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized);
		  return null;

	      }
	  }
	  else {
	      deserialized = serialized;
	  }
      }

      // check hash against current hash
      if (deserialized.hash !== this.hash) {

	  $log.info('Serialized dashboard from storage was stale (old hash: ' + deserialized.hash + ', new hash: ' + this.hash + ')');
	  this.storage.removeItem(this.id);
	  return null;

      }

      // Cache widgets
      var savedWidgetDefs = deserialized.widgets;

      // instantiate widgets from stored data
      for (var i = 0; i < savedWidgetDefs.length; i++) {

	  // deserialized object
	  var savedWidgetDef = savedWidgetDefs[i];

	  // widget definition to use
	  var widgetDefinition = this.widgetDefinitions.getByName(savedWidgetDef.name);

	  // check for no widget
	  if (!widgetDefinition) {
	      // no widget definition found, remove and return false
	      $log.warn('Widget with name "' + savedWidgetDef.name + '" was not found in given widget definition objects');
	      continue;
	  }

	  // check widget-specific storageHash
	  if (widgetDefinition.hasOwnProperty('storageHash') && widgetDefinition.storageHash !== savedWidgetDef.storageHash) {
	      // widget definition was found, but storageHash was stale, removing storage
	      $log.info('Widget Definition Object with name "' + savedWidgetDef.name + '" was found ' +
		'but the storageHash property on the widget definition is different from that on the ' +
		'serialized widget loaded from storage. hash from storage: "' + savedWidgetDef.storageHash + '"' +
		', hash from WDO: "' + widgetDefinition.storageHash + '"');
	      continue;
	  }

	  // push instantiated widget to result array
	  result.push(savedWidgetDef);
      }

      return result;
  },

  _handleAsyncLoad: function (promise) {
      // code same as original post...
  }

};

return DashboardState;
}]);

1 Answer 1

2

As soon as you have async operation, you should make the public API async as well, even if in some or most cases, it is sync. Then the API to the user is consistent.

Instead of "checking for promise", convert the sync call to a promise.

Your load function could then be simplified to the following (I'm leaving some details for brevity, but you should understand the broader concept):

load: function(dashboardId) {

  if (isSync()){
    // sync

    var data = this.storage.getItem(dashboardId);

    // wrap in a promise
    return $q.resolve(_handleSyncLoad(data));

  } else {
    // async

    // return the promise generated by _getItemFromAPI().then()
    return _getItemFromAPI().then(function(data){
      return _handleSyncLoad(data);
    });
  }
}

Note, that I assumed that _getItemFromAPI() returns a promise (in your case it doesn't), so it would something like the following:

function _getItemFromAPI(){

  // ...

  // this "return" is important! it returns the promise generated by $http
  return $http({...}).then(function(response){
    return response.data;
  })
}

This makes the usage of load consistent whether it's sync or async:

dashboardSvc.load(444).then(function(dashboardData){
  $scope.dashboard = dashboardData;
});
Sign up to request clarification or add additional context in comments.

8 Comments

@bob, it's not returned. It should be return dashboardcontext.getDashboardImage()...
I added getDashboardImage function above for reference; but if I understand correctly, you're saying that the Promise is not returned from _getItemFromAPI().
@bob, yes. I can see that getDashboardImage returns a promise, since it's followed by .then, but you also need to return the promise of that .then
I updated my load: function, but getting an error at return this._handleSyncLoad(data); . Looks like it's out of scope when promised is resolved at return _getItemFromAPI().then
@bob, it's not a good idea to edit the question to fit given suggestions - it makes the answers/comments nonsensical to a future user. Please revert to original. Also. it's hard to understand what "getting an error" means specifically. Is this._handleSyncLoad even defined? At any case, you may have broader issues than the scope of your question
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.