Scope issues in my Angular factory code

2019-07-10 00:10发布

问题:

I have an Angular factory called 'DashboardState', which I am currently modifying to handle an API call via my dashboardcontext Angular service layer.

Currently all my widget data is persisted to the localStorage object; however, I am now hooking into a new c# API layer which sets/gets the layout widgets to/from permanent storage (i.e. Sql Server database in this case).

My primary issue is what happens when the promise is returned at return this._getItemFromAPI($rootScope).then of the load: function.

In the load: section, this is a valid object with methods on the stack; however, within the .then section I suddenly lose access to this..

It's a problem for me because I can no longer call this._handleSyncLoad. And as you can see, I've tried a hack by assigning var handleSync = this._handleSyncLoad;, however it doesn't solve the scope issue.:

  if (dashboardId != null && dashboardId != undefined) {

                  this.storage.setItem("defaultDashboardId", dashboardId);

                  var handleSync = this._handleSyncLoad;
                  return this._getItemFromAPI($rootScope).then(function (data) {                           
                      // save current current dashboard id for next time - 06/11/2015 BM:      

                      return handleSync(data, true);    

                   });                      
              }

Here's the full listing of 'DashboardState' :

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 = {
  /**
   * Takes array of widget instance objects, serializes, and saves state.
   * 
   * @param  {Array} widgets  scope.widgets from dashboard directive
   * @return {Boolean}        true on success, false on failure
   */
 save: function (widgets) {             
      // CODE OMITTED FOR SAVE FUNCTION
      return true;
  },                   
 load: function (dashboardId) {        // sent in from navigation-controller call 

      var useLocalStorage = false;     // retrieve from localStorage or via API layer

      var serialized;

      if (useLocalStorage) {           // retrieve dashboard layout from localStorage                  
	      // COME CODE OMITTED FOR BREVITY
	      serialized = this.storage.getItem(dashboardId);                  
	  }
      }
      else {                  

	  // FETCH DASHBOARD HERE !! 
	  if (dashboardId != null && dashboardId != undefined) {

	      // *** "this" is available on the scope at this point ***
	      this.storage.setItem("defaultDashboardId", dashboardId);

	      var handleSync = this._handleSyncLoad;
	      return this._getItemFromAPI($rootScope).then(function (data) {                                                         
		  // *** "this" IS NO LONGER AVAILABLE ON THE SCOPE - i.e. I can no longer call this._handleSyncLoad from here ***
		  return handleSync(data, true);    

	       });                      
	  }
	  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)) {    // && angular.isFunction(serialized.then)) {    // modifed line 09/04/2015 BM:
	      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) {
	  if (data.status == "FAIL") {
	      window.alert("Failed to retrieve dashboard. " + data.messages);
	      return false;
	  }
	  else {                      
	      return data;
	  }
      });            
  },

  _handleSyncLoad: function (serialized, isParsed) {


      // **** MUST HANDLE THE isParsed PARAM; serialized object is alredy parsed ****

      var deserialized, result = [];

      if (!serialized) {
	  return null;
      }

      if (this == undefined) {    // problem if coming from .then of this._getItemFromAPI in load:  - 09/04/2015 BM:

	  deserialized = JSON.parse(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) {
      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;
}]);

Your advice is greatly appreciated.

regards, Bob

回答1:

this always refers to the current function so if you nest function definitions you have to work around it somehow.

This is a typical pattern that should solve your issue:

function someFunction() {
  var that = this;
  doSomething().then(function() {
    // 'that' is your 'this'
  });
}