I have initialized my app following these two guides:
My setup looks something like this:
app.js
function init() {
console.log('running global init()');
window.initgapi();
}
var app = angular.module('app', []);
// Main Controller
app.controller('MainCtrl', ['$scope', '$window', 'cloudendpoints', function($scope, $window, cloudendpoints) {
// this is called once eventapi is loaded
var postInit = function() {
$scope.backend_ready = true;
$scope.fetchContent();
};
$window.initgapi = function() {
cloudendpoints.init(postInit);
};
$scope.fetchContent = function() {
gapi.client.cloudendpoints
.getContent()
.execute(function(resp) {
// do something with response
});
};
}]);
The cloudendpoints service is in its own file, cloudendpoints.js
:
// for loading endpoints service
app.factory('cloudendpoints', [function cloudendpoints() {
var init = function(postInit) {
var restUrl = '//' + window.location.host + '/_ah/api';
gapi.client.load('cloudendpoints', 'v1', postInit, restUrl);
};
return { init: init };
}]);
Lastly, our scripts are loaded in this order:
<script src="angular.min.js"></script>
<script src="app.js"></script>
<script src="controllers/mainCtrl.js"></script>
<script src="services/cloudendpoints.js"></script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
The Challenge
This works well so far, because we are only using a single controller (MainCtrl
). This is what happens in our code:
init()
, which calls window.loadCloudEndpoints()
cloudendpoints.init(postInit)
loads the cloudendpoints endpoint service, which then calls the postInit()
callback. We can then make calls to the endpoints API from within the postInit()
.The challenge arises when we want to create another controller to handle another view of our app. Let's say we create a ContentPageCtrl
controller — do we then need to once again init our endpoint service? How can we make the endpoint service available to all controllers without having to repeat ourselves?
My Hacky Solution
In order to get around this, I $watch
the the backend_ready
so that I may only start making gapi calls after the endpoints api has loaded:
app.controller('ContentPageCtrl', ['$scope', function($scope) {
/**
* Make sure that the backend is ready before
* running any gapi.client.cloudendpoints calls
**/
$scope.$watch('backend_ready', function() {
if ($scope.backend_ready === true) {
gapi.client.cloudendpoints
.loadContent()
.execute(function(resp) {
// put content in DOM
});
}
});
}]);
This means I would need to $watch
the backend_ready
variable in every controller in which I need to make endpoint calls. My approach feels pretty dirty, and has problems scaling.
What is the better approach to this?
A better approach to this would be to leverage the power of Promises
. you can then have your Service init once (inside the service function) and on every method you simply call promise.then(...)
and keep the logic specific to this method. take this as an example:
app.factory('cloudendpoints', ['$q','$timeout','$window',function cloudendpoints($q,$timeout,$window) {
var backend_ready = $q.defer();
checkLoaded();
function checkLoaded(){
if($window.gapi)
backend_ready.resolve();
else
$timeout(checkLoaded,100); //check again in 100ms
}
var init = function(postInit) {
var restUrl = '//' + window.location.host + '/_ah/api';
return backend_ready.promise.then(function(resp){
gapi.client.load('cloudendpoints', 'v1', postInit, restUrl);
}); //we are returning a promise so we can have more
//flexability inside the controllers (do stuff after the loaded api);
};
return {
init: init
};
}]);
//Somewhere inside a controller...
app.controller('someCtrl', ['cloudendpoints', function(cloudendpoints){
function postInit(){ ... }
cloudendpoints.init(postInit); //waits for gapi to load, then executes init
});