rGil
rGil

Reputation: 3719

How to communicate with server using AngularJS within Google Apps Script

Recently it has become possible to use angularjs within google apps script via the iframe sandbox mode.

My problem comes when trying to communicate with the server (gapps spreadsheet) and receiving asynchronous data in return.

The implementation for receiving data from the server is to use a function with a callback function like so:

google.script.run.withSuccessHandler(dataGatheringFunction).getServerData();

getServerData() would be a function that resides server-side that would return some data, usually from the accompanying spreadsheet. My question is how to use the callback function within the parameters of AngularJS. A typical $http function could be placed in a provider, and the scope value could be populated after then.() returns. I could also invoke $q. But how would I deal with the necessity of google's callback?

Here's a simplified version of what I'm messing with so far:

app.factory("myFactory", function($q){

  function ssData(){
  var TssData = function(z){
  return z;
}
google.script.run.withSuccessHandler(TssData).getServerData();
var deferred = $q.defer();
var d = deferred.resolve(TssData)
console.log("DP: " + deferred.promise);
return deferred.promise;
}  
return ssData();
})

Then in the controller resolve the server call similar to this: myFactory.then(set some variables here with the return data)

My question is simply - How do I deal with that callback function in the provider?

The script throws no errors, but does not return the data from the server. I could use the old $timeout trick to retrieve the data, but there should be a better way.

Upvotes: 2

Views: 2999

Answers (4)

BMcV
BMcV

Reputation: 613

Maybe the most elegant solution that makes sure the google.script.run callbacks are registered automatically in the AngularJS digest cycle would be to use the $q constructor to promisify the google callbacks. So, using your example above:

app.factory('myFactory', ['$q', function ($q){

  return {ssData: ssData};

  function ssData(){
    var TssData = function(z){
      return z;
    };

    var NoData = function(error) {
      // Error Handling Here
    };

    return $q(function(resolve, reject) {
      google.script.run
        .withSuccessHandler(resolve)
        .withFailureHandler(reject)
        .getServerData();
    }).then(TssData).catch(NoData);
  }

}]);

Then in your controller you can call myFactory.ssData()

Since I don't know exactly what TssData is doing I included it here but note that this simply returns another promise in this context which you will still have to handle in your controller:

myFactory.ssData().then(function(response) {
  // Set data to the scope or whatever you want
});

Alternately, you could expose TssData by adding it to the factory's functions if it is doing some kind of data transformation. If it is truly just returning the response, you could refactor the code and omit TssData and NoData and handle the promise entirely in the controller:

app.factory('myFactory', ['$q', function ($q){

  return {ssData: ssData};

  function ssData(){
    return $q(function(resolve, reject) {
        google.script.run
          .withSuccessHandler(resolve)
          .withFailureHandler(reject)
          .getServerData();
    });
  }
}]);

app.controller('myController', ['myFactory', function(myFactory) {
  var vm = this;
  myFactory.ssData()
    .then(function(response) {
      vm.myData = response;
    }).catch(function(error) {
      // Handle Any Errors
    });
}]);

An excellent article about promises (in Angular and otherwise) is here: http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

Upvotes: 4

Javier K
Javier K

Reputation: 105

You only need to $apply the output from the server function:

google.script.run.withSuccessHandler(function(data) {

  $scope.$apply(function () {
    $scope.data = data;
  });

}).withFailureHandler(errorHandler).serverFunction();

Upvotes: 5

Andrew Roberts
Andrew Roberts

Reputation: 2808

This guy seems to be pulling data from a GSheet into angular quite happily without having to do anything fancy.

function gotData(res) {
  $scope.validUser = res.validUser;
  var data = angular.copy(res.data), obj, i=0;
  Object.keys(data).forEach(function(sh) {
    obj = {title: sh, checked: {}, showFilters: false, search: {}, sort: {index: 0, reverse: false}, currentPage: 0, checkedAll: true, showBtns: true, searchAll: ''};
    obj.heading = data[sh].shift();
    obj.list = data[sh];
    obj.heading.forEach(function(s,i) {
      obj.checked[i] = true;
    });
    $scope.sheets.push(obj);
  });
  $scope.sheets.sort(function(a,b) {
    return a.title > b.title ? 1 : -1;
  });
  $scope.gotData = true;
  $scope.$apply();
}
google.script.run.withSuccessHandler(gotData).withFailureHandler($scope.gotError).getData();

Upvotes: 3

rGil
rGil

Reputation: 3719

My solution was to get rid of the $q, promise scenario all together. I used $rootScope.$broadcast to update scope variables from the server.

Link to spreadsheet with script.

Upvotes: 2

Related Questions