Trouble signing out with Google Sign in (gapi) with angularjs - javascript

So I have tried the following templates trying to integrate this:
HTML:
<google-sign-in-button button-id="login-button" options="options"></google-sign-in-button>
CSS:
.directive('googleSignInButton', function() {
return {
scope: { buttonId: '#', options: '&' },
template: '<div></div>',
link: function(scope, element, attrs) {
var div = element.find('div')[0];
div.id = attrs.buttonId;
gapi.signin2.render(div.id, scope.options());
}
};
})
--
I've also just tried doing this in the header and using the regular sign in button:
HTML:
<div class="g-signin2" data-onsuccess="onSignIn"></div>
IN THE HEADER:
<script>
window.onLoadCallback = function(){
gapi.auth2.init({
client_id: '123.apps.googleusercontent.com'
});
}
</script>
No matter what I do, i can't figure out how to log a user out. In my controller, when i try and do gapi.auth.signOut(); it says gapi is undefined
EDIT:
I've also tried dabbling in this to log a person out on run but ideally i'd want to make a log out work anywhere and not just when the page loads. I just don't really know where to put it or the correct way to make it happen:
.run(function ($rootScope, $state) {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init();
auth2.then(function(){
auth2.signOut();
});
});
})
EDIT #2:
I also tried to create this factory with a resolve on my ui-router but it's not getting the data in time or at all
.factory('Auth', function($http, $state, $q, $rootScope) {
var factory = { loggedIn: loggedIn };
return factory;
function loggedIn() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init();
auth2.then(function(){
return auth2.isSignedIn.get();
});
});
}
})
EDIT #3:
I tried creating a service but I keep getting the following error for some reason to even test it:
Error: undefined is not an object (evaluating 'gapi.auth2.init')
.service('googleService', ['$http', '$rootScope', '$q', function ($http, $rootScope, $q) {
var self = this;
this.signedIn = function() {
auth2 = gapi.auth2.init();
auth2.then(function(){
return auth2.isSignedIn.get();
});
}
this.signOut = function(){
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
console.log('User signed out.');
});
}
}])
.controller('login', ['$rootScope', '$scope', '$q', 'googleService', function ($rootScope, $scope, $q, googleService) {
console.log(googleService.signedIn());
}])

I build upon my fiddle from my previous answer on a related question.
Basically what I added was a function to the controller scope that would be called when a user clicks on the signout button.
angular.module('app', [])
.controller('MainController', ['$scope',
function($scope) {
$scope.isSignedIn = false;
...
$scope.signOut = function(){
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
$scope.$apply(function(){
$scope.isSignedIn = false;
console.log('User signed out.');
});
});
}
}
])
I used the code snippet provided by Google documentation and that seemed to work immediately.
Do pay attention when changing variables in scope, you have to wrap your scope changes in $scope.$apply for angular to force to check changes in scope.
You can find the full code in this fiddle.
(I will be removing this Google api project at some point, so replace the API Key with your own if it doesn't work)
This is demo code, so if you would actually put this in project, I'd recommend hiding some of the complexity behind services and directives.
Update: if you want to use a service, you'll have to use angulars promises heavily, see $q docs.
Here's a sample on how you could create a service using promises and callbacks.
There's no simple way to get around the callback hell. But I hope wrapping these things into a service will solve that partially.
Here's an updated fiddle taking advantage of the service.
This is the js code:
angular.module('app', [])
.controller('MainController', ['$scope','googleService',
function($scope, googleService) {
$scope.isSignedIn = false;
googleService.load().then(function(){
$scope.signIn = function(){
googleService.signIn().then(function(){
$scope.isSignedIn = googleService.isSignedIn();
});
};
$scope.signOut = function(){
googleService.signOut().then(function(){
$scope.isSignedIn = googleService.isSignedIn();
});
};
});
}
])
.service('googleService', ['$q', function ($q) {
var self = this;
this.load = function(){
var deferred = $q.defer();
gapi.load('auth2', function(){
var auth2 = gapi.auth2.init();
//normally I'd just pass resolve and reject, but page keeps crashing (probably gapi bug)
auth2.then(function(){
deferred.resolve();
});
addAuth2Functions(auth2);
});
return deferred.promise;
};
function addAuth2Functions(auth2){
self.signIn = function() {
var deferred = $q.defer();
auth2.signIn().then(deferred.resolve, deferred.reject);
return deferred.promise;
};
self.isSignedIn = function(){
return auth2.isSignedIn.get();
}
self.signOut = function(){
var deferred = $q.defer();
auth2.signOut().then(deferred.resolve, deferred.reject);
return deferred.promise;
};
}
}]);
Basically, inside the load function you wrap the complexity of loading gapi, and auth2. After the load promise resolved in your controller, you are certain that the signIn, signOut, etc will work because it is loaded.

I took a slightly different approach. Though, I am not good enough to explain why your code does not work and this works for me. Hopefully, someone else can help on this.
Here is my approach. Let me know if this helps.
login.html
<script>var gapiOnLoadCallback = function() { window.gapiOnLoadCallback(); }</script>
<script src="https://apis.google.com/js/platform.js?onload=gapiOnLoadCallback" async defer></script>
<div id="googleLoginButton"></div>
<button ng-show="signedIn" ng-click="signOut()">Sign Out</button>
login.js
angular.module('app', [])
.controller('LoginController', ['$window','$scope', function($window, $scope) {
$window.gapiOnLoadCallback = function() {
gapi.signin2.render('googleLoginButton', {
'onsuccess': onSuccess,
'onfailure': onFailure
});
}
var onSuccess = function(googleUser) {
// Success handling here
};
var onFailure = function() {
// Failure handling here
};
$scope.signOut = function() {
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
// Some UI update
});
};
};
login-directive.js
angular.module('app', []).directive("loginButton", function() {
return {
restrict: 'E',
scope: {},
templateUrl: 'login/login.html',
controller: 'LoginController'
}
});

Related

AngularJS scope variables not updating in jQuery promise/deferred

I am building a single page application on AngularJS and I have a controller set up with a function that is run on a button click. This function runs with a promise. When the function is resolved I am updating a root variable and changing the $location path. But the root variable and $location dont seem to be updating.
Please note this all code is exampled from production
DOM:
<div ng-controller="ExampleController">
<button ng-click="button_function('I am a variable')">Run function</button>
</div>
Controller:
app.controller('ExampleController', ['$scope', '$location', function($scope, $location) {
$scope.button_function = function(variable) {
$scope.$root.show_something = true;
my_function.something(variable).done(function(data) {
if (data) {
$scope.$root.show_something = false;
$location.path('/go-to-path');
} else {
alert('Something went wrong');
}
}]);
};
}]);
This is the my_function code:
var my_function = {
something: function(variable) {
var deferred = $.Deferred();
var window = window.open('http://dynamic.url/', '_blank');
$(window).on('loadstart', function(e) {
var url = e.originalEvent.url;
if (url === 'http://dynamic.url/expected_response') {
window.close();
deferred.resolve({
key_1: 'data',
key_2: 'more data'
});
}
});
return deferred.promise();
}
};
All looks good right? But when the my_function.something(variable) is "done" the $location and $scope.$root.show_something don't seem to update.
Am I doing something wrong?
Thanks
You should return deferred.promise instead of deferred.promise().
--edit: my bad I didn't see you are not using $q as I misread.
I have found the fix.
In my controller after the deferred is "done" I wrapped my variables in $timeout
app.controller('ExampleController', ['$scope', '$location', '$timeout', function($scope, $location, $timeout) {
$scope.button_function = function(variable) {
$scope.$root.show_something = true;
my_function.something(variable).done(function(data) {
if (data) {
$timeout(function() {
$scope.$root.show_something = false;
$location.path('/go-to-path');
}, 1);
} else {
alert('Something went wrong');
}
}]);
};
}]);
Answer found here

AngularJS $log.debug is not a function

I'm a bit frustrated because I can't figure out whats up with the AngularJS $log service. I don't use it often but I have it working on another part of my site, the only reason I could think it wouldn't be working would be something to do with the block scope inside the then function. In my console I get the following error when trying to run $log.debug:
TypeError: $log.debug is not a function
at analysis.client.controller.js:23
at processQueue (angular.js:14551)
at angular.js:14567
at Scope.$get.Scope.$eval (angular.js:15830)
at Scope.$get.Scope.$digest (angular.js:15641)
at angular.js:15869
at completeOutstandingRequest (angular.js:5387)
at angular.js:5659
Heres my analysis.client.controller.js file:
'use strict';
angular.module('analysis').controller('AnalysisController', ['$scope', '$timeout', '$mdSidenav', '$mdComponentRegistry', '$log', 'Authentication',
function($scope, $timeout, $mdSidenav, $mdComponentRegistry, $log, Authentication) {
$scope.user = Authentication.user;
// Option #1
//
// $scope.isOpen = function() { return $mdSidenav('right').isOpen(); };
// $scope.toggle = function() { $mdSidenav('right').toggle() };
// Option #2 - See https://github.com/angular/material/issues/974
$scope.toggle = angular.noop;
$scope.isOpen = function() {
return false;
};
$scope.toggleLeft = function() {
$mdSidenav('left').toggle()
.then(function() {
$log.debug('toggle left is done');
});
};
}
]);
Thanks in advance for any help!
The debug method is not enabled by default because not all browsers support console.debug, but you can enable it in the configuration phase of your app with the $logProvider.
'use strict';
angular.module('analysis')
.config(function($logProvider){
$logProvider.debugEnabled(true);
})
.controller('AnalysisController', function(
$scope,
$timeout,
$mdSidenav,
$mdComponentRegistry,
$log,
Authentication
) {
$scope.user = Authentication.user;
// Option #1
//
// $scope.isOpen = function() { return $mdSidenav('right').isOpen(); };
// $scope.toggle = function() { $mdSidenav('right').toggle() };
// Option #2 - See https://github.com/angular/material/issues/974
$scope.toggle = angular.noop;
$scope.isOpen = function() {
return false;
};
$scope.toggleLeft = function() {
$mdSidenav('left').toggle().then(function() {
$log.debug('toggle left is done');
});
};
});
$log's debug method is disabled by default. You need to enable $log from app config phase like below.
app.config(function($logProvider){
$logProvider.debugEnabled(true);
});
FYI, I got this same error when I inadvertently reassigned the debug method instead of calling it.
$log.debug = 'My log message'
Instead of:
$log.debug('My log message')
After browsing to the page that had the bad statement, I would get "TypeError: $log.debug is not a function" on my other pages that had log statements.

Angularjs cacheFactory : id is already taken

I am using the $cacheFactory to store a language JSON file for the app, and i run a factory method every new route like this:
index.js
$routeProvider
.when('/',{
'templateUrl':'views/home/index.html',
'controller':'Home',
'resolve': {
'onEnter': function ($rootScope, langFactory) {
return langFactory.getLangFile($rootScope.lang.appLang);
}
}
})
.when('/auth/login',{
'templateUrl':'views/auth/login.html',
'controller':'AuthLogin',
'resolve': {
'onEnter': function ($rootScope, langFactory) {
return langFactory.getLangFile($rootScope.lang.appLang);
}
}
})
factories.js
.factory('langFactory', ['$rootScope', '$window', '$http', '$cacheFactory', '$q', function ($rootScope, $window, $http, $cacheFactory, $q) {
var getLangFile = function (langCode) {
var deferred = $q.defer()
, cache = $cacheFactory('langCache');
if (!!!cache.get('cache' + $rootScope.lang.appLang)) {
$http.get(langCode + '-langFile-to-be-REMOVED.json').success(function (response) {
cache.put('cache' + $rootScope.lang.appLang, response);
deferred.resolve();
}).error(function (err) {
$window.console.error('Unable to retrieve app language: ' + err);
deferred.reject(err);
});
} else {
deferred.resolve();
}
return deferred.promise;
};
return {
'getLangFile':getLangFile
};
}])
On first page load it works , then if i browse, without refreshing, to auth/login i get a console error:
[$cacheFactory:iid] CacheId 'langCache' is already taken!
Seems like (since i call the factory method on every route) it cant use the same id !?
I actually dont know what to do to fix this, any help appriciated, thanks.
This is what worked for me:
cache = $cacheFactory.get('langCache') || $cacheFactory('langCache');
Ok thanks to a guy on IRC i fixed this problem, i just had to change this:
var getLangFile = function (langCode) {
var deferred = $q.defer()
, cache = $cacheFactory('langCache');
to
var cache = $cacheFactory('langCache')
, getLangFile = function (langCode) {
var deferred = $q.defer();

Architecture of single method for ajax in Angularjs

I'd like to understand how can i design single ajax-method for several controllers, which also can influence on user interface ('loading' animation, for example).
Idea is (without promises):
var myApp = angular.module('myApp', []);
myApp.controller('myCtrl',
function myCtrl($scope, myFactory){
$scope.loading = false;
$scope.someStuff = myFactory.getStuff(params);
});
myApp.factory('myFactory', function(myService){
return{
getStuff: function(params){
return myService.ajax(params);
}
}
});
myApp.service('myService', function($http) {
this.ajax = function(params){
// switch $scope.loading = true;
// make request
// return $http result
// switch $scope.loading = false;
};
});
As i know, i need use $scope for UI changes and ajax-method should be taken out to custom service. Services in Angularjs does not work with $scope and i have no idea how can i solve this problem.
I think, there must be a service with chain of promises.
How can it be designed?
Upd: I hope, with the time the documentation will be more complete and clear. But community of angular users is already great. Thanks.
In my project I have defined a service called appState which has (among other) methods: showGlobalSpinner and hideGlobalSpinner which modify a variable on the $rootScope.
Basically:
(…)
.factory('appState', ['$rootScope', function ($rootScope) {
return {
showGlobalSpinner: function () {
++$rootScope.loadingInProgress;
},
hideGlobalSpinner: function () {
if ($rootScope.loadingInProgress > 0) {
--$rootScope.loadingInProgress
}
}
};
}]);
What I do next is I show spinners wherever I need them using ng-show directive:
<div class="spinner" ng-show="loadingInProgress"></div>
Before each AJAX I just call AppState.showGlobalSpinner() and after success/error I call AppState.hideGlobalSpinner()
You could add this method to any of your controllers:
.controller('HeaderCtrl',['$scope','httpRequestTracker', function($scope,httpRequestTracker) {
$scope.hasPendingRequests = function () {
return httpRequestTracker.hasPendingRequests();
};
}]);
angular.module('services.httpRequestTracker', []);
angular.module('services.httpRequestTracker').factory('httpRequestTracker', ['$http', function($http){
var httpRequestTracker = {};
httpRequestTracker.hasPendingRequests = function() {
return $http.pendingRequests.length > 0;
};
return httpRequestTracker;
}]);
You can try a more generic approach by using an HTTP interceptor, some events and a directive:
Javascript
app.factory('myHttpInterceptor', function($q, $rootScope) {
return function(promise) {
$rootScope.$broadcast('RequestStarted');
var success = function(response) {
$rootScope.$broadcast('RequestFinished', true);
};
var error = function(response) {
$rootScope.$broadcast('RequestFinished', false);
return $q.reject(response);
};
promise.then(success, error);
return promise;
}
})
.config(function($httpProvider) {
$httpProvider.responseInterceptors.push('myHttpInterceptor');
})
.directive('loading', function() {
return {
restrict: 'E',
transclude: true,
template: '<div ng-show="visible" ng-transclude></div>',
controller: function($scope) {
$scope.visible = false;
$scope.$on('RequestStarted', function() {
$scope.visible = true;
});
$scope.$on('RequestFinished', function() {
$scope.visible = false;
});
}
};
});
HTML
...
<loading><h1>Loading...</h1></loading>
...
You can see a working demo here.
By using an HTTP interceptor, you'll be able to track every HTTP request made by the $http service ($resource included) across you Angular application and show the load animation accordingly.

How do I mock the result in a $http.get promise when testing my AngularJS controller?

After much reading, it seems that the recommended way to call a web service from an AngularJS controller is to use a factory and return a promise from that.
Here I have a simple factory which calls a sample API.
myApp.factory('MyFactory', ['$http',function($http) {
var people = {
requestPeople: function(x) {
var url = 'js/test.json';
return $http.get(url);
}
};
return people;
}]);
And this is how I call it in the controller
myApp.controller('MyCtrl1', ['$scope', 'MyFactory', function ($scope, MyFactory) {
MyFactory.requestPeople(22).then(function(result) {
$scope.peopleList = result;
});
}]);
While it works fine, I would like to be able to mock the result that is passed in when then is called. Is this possible?
My attempt so far has produced nothing. This is my attempt:
//Fake service
var mockService = {
requestPeople: function () {
return {
then: function () {
return {"one":"three"};
}
}
}
};
//Some setup
beforeEach(module('myApp.controllers'));
var ctrl, scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('MyCtrl1', { $scope: scope, MyFactory: mockService });
}));
//Test
it('Event Types Empty should default to false', inject(function () {
expect(scope.peopleList.one).toBe('three');
}));
The error that I get when running this in karma runner, is
TypeError: 'undefined' is not an object (evaluating 'scope.peopleList.one')
How can I get this test working with my mocked data?
I don't think $httpBackend is what you're after here, you want the whole factory to be mocked without it having a dependency on $http?
Take a look at $q, in particular the code sample under the Testing header. Your issue might be resolved with code that looks like this:
'use strict';
describe('mocking the factory response', function () {
beforeEach(module('myApp.controllers'));
var scope, fakeFactory, controller, q, deferred;
//Prepare the fake factory
beforeEach(function () {
fakeFactory = {
requestPeople: function () {
deferred = q.defer();
// Place the fake return object here
deferred.resolve({ "one": "three" });
return deferred.promise;
}
};
spyOn(fakeFactory, 'requestPeople').andCallThrough();
});
//Inject fake factory into controller
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
q = $q;
controller = $controller('MyCtrl1', { $scope: scope, MyFactory: fakeFactory });
}));
it('The peopleList object is not defined yet', function () {
// Before $apply is called the promise hasn't resolved
expect(scope.peopleList).not.toBeDefined();
});
it('Applying the scope causes it to be defined', function () {
// This propagates the changes to the models
// This happens itself when you're on a web page, but not in a unit test framework
scope.$apply();
expect(scope.peopleList).toBeDefined();
});
it('Ensure that the method was invoked', function () {
scope.$apply();
expect(fakeFactory.requestPeople).toHaveBeenCalled();
});
it('Check the value returned', function () {
scope.$apply();
expect(scope.peopleList).toBe({ "one": "three" });
});
});
I've added some tests around what $apply does, I didn't know that until I started playing with this!
Gog

Categories

Resources