Load Google JS Client library in AngularJS - angularjs

I created an oauth2 service to use a Google API. The following line loads the JS Client library and registers an onload handler which fires when the library has finished loading:
<script src="https://apis.google.com/js/client.js?onload=load"></script>
My angular app is defined:
var myapp = angular.module('myapp', ['ui.state', 'ui.bootstrap', '$strap.directives']);
and the service
myapp.factory('oauth2', function($rootScope) {
this.load = function() {
console.log("Load...");
gapi.client.load('oauth2', 'v2', function() {
console.log("OAuth2 API loaded");
});
};
...
return this;
});
The load callback is not called. I also tried oauth2.load, myapp.load but none of these work. How do you configure the onload, to call oauth2.load?

This is a complete guess, and may not even be close, but perhaps it can spurn others to take a look.
My guess involves accessing the angular service from outside angular. I'm using this post as a reference:
// since the callback is referencing the function 'load', define it here:
var load = function(){
var elem = angular.element(document.querySelector('[ng-controller]'));
var injector = elem.injector();
var oauth2 = injector.get('oauth2') // grab the factory
return oauth2
}
Again - just a guess. If it doesn't work, comment below and I'll delete!!

Related

Why is Angular not updating with a JSON file?

I'm trying to use a simple Angular JS app to load data from a JSON file to a website but it does not work.
The JSON file is:
{"a": "a"}
The Angular app is:
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(data) {
vm.data = data;
});
}])
.service("ser", function() {
this.getInfo = function() {
return $.get("models/model.json");
};
});
The HTML is:
<div ng-controller="ctrl as ctrl">
<p>{{ctrl.data.a}}</p>
</div>
I'm not getting any console errors. I think the problem is related to the lexical scoping for the controller due to the asynchronous getInfo().then() call in the controller, I checked vm inside the function and it is being loaded correctly but doesn't seem to change the ctrl object or Angular is not updating when it does.
I'm serving the app locally.
It works sometimes but most times it doesn't. I can get it to work using $scope but I'm trying to figure out why it's not working now.
It appears you are using jQuery for the ajax. If you modify the scope outside of angular context you need to notify angular to run a digest
Change to using angular $http to avoid such issues
var app = angular.module("app", [])
.controller("ctrl", ["ser", function(ser) {
var vm = this;
ser.getInfo().then(function(response) {
vm.data = response.data;
});
}])
.service("ser", ['$http', function($http) {
this.getInfo = function() {
return $http.get("models/model.json");
};
}]);
DEMO
If it works with $scope that means that without it, Angular is not aware that you performed an asynchronous operation.
I think the following line is using jQuery: return $.get("models/model.json");
So even if you get your data from your function getInfo, it isn't synchronized with the view via vm.data = data;

AngularJS + Cloud Endpoints: loading endpoints api across multiple controllers

I have initialized my app following these two guides:
AngularJS + Cloud Endpoints -- A Recipe for Building Modern Web Applications
Angular Js and google api client.js (gapi)
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:
The gapi client loads, then calls 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 ContentPageCtrlcontroller — 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
});

AngularJS testing with Jasmine

I'm a newbie to programming and I'm trying to figure out how to unit test angularJS code with jasmine, and its driving me insane!
This is the angular code im trying to test, its all set up on an asp.net web application using abpBoilerplate and angular. The result of the code below is that when a button is clicked on the web page, a 'success' popup appears and "true" appears in a text box, if the service is available. The service is being pulled from classes within a web api project.
(function() {
var controllerId = 'app.views.home';
angular.module('app').controller(controllerId, [
'$scope', 'abp.services.lfcservice.webapi', function($scope,lfcServices) {
var vm = this;
//Home logic...
vm.CheckLfcIsAvailable = function () {
lfcServices.lfcIsAvailable()
.success(function () {
abp.notify.info('Success');
vm.Available = 'True';
});
};
I just need to know how to write a jasmine test that passes when it expects a true value for the lfc service. Ive tried loads of different combinations with no success, I could paste in 10 different attempts ive had in here but they are all very different.
Any help would be much appreciated!
First, you need to know how to test a controller, mocking the service.
Then, you need to mock the service API to return a promise.
let's say thet the controller is initiated with Available = false;.
Test an angular 1.x controller (see jsFiddle):
describe("app.views.home controller spec", function() {
var ctrl;
//depend on the module
beforeEach(module('app'));
beforeEach(inject(function($controller) {
//use angular's "$controller" to get the controller
ctrl = $controller("app.views.home");
}));
it("available should be false", function() {
expect(ctrl.Available).toBe(false);
});
});
Now, let's asume that the service returns a simple result (without promises) and see how do we provide a mock service instead of the real service.
Test an angular 1.x controller with mock service (see jsFiddle):
beforeEach(module(function($provide) {
var mockService = jasmine.createSpyObj('mock', ['lfcIsAvailable']);
mockService.lfcIsAvailable.and.returnValue(true);
$provide.value('abp.services.lfcservice.webapi', mockService);
}));
Now, let's see how to mock a promise response. for this we will use $q.
Mock angular 1.x promise (see jsFiddle):
it('should change after promise resolved', inject(function($q, $rootScope) {
//create promise
var deferred = $q.defer();
//mock service response
mockService.lfcIsAvailable.and.returnValue(deferred.promise);
//call CheckLfcIsAvailable ()
ctrl.CheckLfcIsAvailable ();
expect(ctrl.Available).toBe(false);
deferred.resolve(true);
//not yet...
expect(ctrl.Available).toBeNull(false);
//from angular $q documentation:
//"it's important to know that the resolution of promises is tied to the digest cycle"
$rootScope.$apply();
//now!
expect(ctrl.Available).toBe(true);
}));

Firebase's AngularFire in an AngularJS service

The best way of handling Firebase in AngularJS surely has to be from within a service, so it's available to all Controllers across the App.
I just can't get it to work! ... I first tried using angularFire(new Firebase(url)), hoping I could bind to the service's scope, but Angular complains that it cannot $watch it.
So I tried angularFireCollection instead like this:
app.factory('myService', function myService(angularFireCollection) {
var url = 'https://myfirebase.firebaseio.com';
return {
getAll: function(path) {
var ref = angularFireCollection(new Firebase(url + '/' + path));
console.log(ref);
return ref;
},
...
};
});
However, the angularFireCollection is an Object containing a load of methods etc. if I bind it to a controller $scope I just get garbage. It also complains that it can't call the Firebase functions before I try to use them (e.g. Error: Firebase.push failed: second argument must be a valid function.)... anyone got any ideas where I'm going wrong?
See this PLUNKER
If you want to encapsulate some of the functionality into a service, consider keeping the returned ref in state of the service. I expanded on your plunker. It seems to mostly do what you were trying for.
http://plnkr.co/edit/Uf2fB0
Jeff answered the question correctly ... I'm just posting a further development on Jeff's example for those who are interested.
I have abstracted the Firebase service creation, so you can dynamically create an instance of whatever Firebase service you want:-
var registerFirebaseService = function (serviceName) {
app.factory(serviceName, function (angularFire) {
var _url = null;
var _ref = null;
return {
init: function (url) {
_url = url;
_ref = new Firebase(_url);
},
setToScope: function (scope, localScopeVarName) {
angularFire(_ref, scope, localScopeVarName);
}
};
});
};
You first create an instance of the service as follows
registerFirebaseService('itemsService'); // create itemsService instance
Then you can inject the itemsService service into your controllers. The instance is initialised using your Firebase URL e.g.
itemsService.init('https://firebase.firebaseio.com/' + userId + '/items');
The Firebase can now be bound to your controller e.g.
itemsService.setToScope($scope, 'items');
adapted PLUNKER

AngularJS: how do I use an angular service during module configuration time?

See this plunkr for a live example: http://plnkr.co/edit/djQPW7g4HIuxDIm4K8RC
In the code below, the line var promise = serviceThatReturnsPromise(); is run during module configuration time, but I want to mock out the promise that is returned by the service.
Ideally I'd use the $q service to create the mock promise, but I can't do that because serviceThatReturnsPromise() is executed during module configuration time, before I can get access to $q. What's the best way to resolve this chicken and egg problem?
var app = angular.module('plunker', []);
app.factory('serviceUnderTest', function (serviceThatReturnsPromise) {
// We mock out serviceThatReturnsPromise in the test
var promise = serviceThatReturnsPromise();
return function() {
return 4;
};
});
describe('Mocking a promise', function() {
var deferredForMock, service;
beforeEach(module('plunker'));
beforeEach(module(function($provide) {
$provide.factory('serviceThatReturnsPromise', function() {
return function() {
// deferredForMock will be undefined because this is called
// when `serviceUnderTest` is $invoked (i.e. at module configuration),
// but we don't define deferredForMock until the inject() below because
// we need the $q service to create it. How to solve this chicken and
// egg problem?
return deferredForMock.promise;
}
});
}));
beforeEach(inject(function($q, serviceUnderTest) {
service = serviceUnderTest;
deferredForMock = $q.defer();
}));
it('This test won\'t even run', function() {
// we won't even get here because the serviceUnderTest
// service will fail during module configuration
expect(service()).toBe(4);
});
});
I'm not sure I like the solution much, but here it is:
http://plnkr.co/edit/uBwsJxJRjS1qqsKIx5j7?p=preview
You need to ensure that you don't instantiate "serviceUnderTest" until after you've set-up everything. Therefore, I've split the second beforeEach into two separate pieces: the first instantiates and uses $q, the second instantiates and uses serviceUnderTest.
I've also had to include the $rootScope, because Angular's promises are designed to work within a $apply() method.
Hope that helps.

Resources