Auto logout with Angularjs based on idle user - angularjs

Is it possible to determine if a user is inactive and automatically log them out after say 10 minutes of inactivity using angularjs?
I was trying to avoid using jQuery, but I cannot find any tutorials or articles on how to do this in angularjs. Any help would be appreciated.

I wrote a module called Ng-Idle that may be useful to you in this situation. Here is the page which contains instructions and a demo.
Basically, it has a service that starts a timer for your idle duration that can be disrupted by user activity (events, such as clicking, scrolling, typing). You can also manually interrupt the timeout by calling a method on the service. If the timeout is not disrupted, then it counts down a warning where you could alert the user they are going to be logged out. If they do not respond after the warning countdown reaches 0, an event is broadcasted that your application can respond to. In your case, it could issue a request to kill their session and redirect to a login page.
Additionally, it has a keep-alive service that can ping some URL at an interval. This can be used by your app to keep a user's session alive while they are active. The idle service by default integrates with the keep-alive service, suspending the pinging if they become idle, and resuming it when they return.
All the info you need to get started is on the site with more details in the wiki. However, here's a snippet of config showing how to sign them out when they time out.
angular.module('demo', ['ngIdle'])
// omitted for brevity
.config(function(IdleProvider, KeepaliveProvider) {
IdleProvider.idle(10*60); // 10 minutes idle
IdleProvider.timeout(30); // after 30 seconds idle, time the user out
KeepaliveProvider.interval(5*60); // 5 minute keep-alive ping
})
.run(function($rootScope) {
$rootScope.$on('IdleTimeout', function() {
// end their session and redirect to login
});
});

View Demo which is using angularjs and see your's browser log
<!DOCTYPE html>
<html ng-app="Application_TimeOut">
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
</head>
<body>
</body>
<script>
var app = angular.module('Application_TimeOut', []);
app.run(function($rootScope, $timeout, $document) {
console.log('starting run');
// Timeout timer value
var TimeOutTimerValue = 5000;
// Start a timeout
var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
var bodyElement = angular.element($document);
/// Keyboard Events
bodyElement.bind('keydown', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('keyup', function (e) { TimeOut_Resetter(e) });
/// Mouse Events
bodyElement.bind('click', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousemove', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('DOMMouseScroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousewheel', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousedown', function (e) { TimeOut_Resetter(e) });
/// Touch Events
bodyElement.bind('touchstart', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('touchmove', function (e) { TimeOut_Resetter(e) });
/// Common Events
bodyElement.bind('scroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('focus', function (e) { TimeOut_Resetter(e) });
function LogoutByTimer()
{
console.log('Logout');
///////////////////////////////////////////////////
/// redirect to another page(eg. Login.html) here
///////////////////////////////////////////////////
}
function TimeOut_Resetter(e)
{
console.log('' + e);
/// Stop the pending timeout
$timeout.cancel(TimeOut_Thread);
/// Reset the timeout
TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
}
})
</script>
</html>
Below code is pure javascript version
<html>
<head>
<script type="text/javascript">
function logout(){
console.log('Logout');
}
function onInactive(millisecond, callback){
var wait = setTimeout(callback, millisecond);
document.onmousemove =
document.mousedown =
document.mouseup =
document.onkeydown =
document.onkeyup =
document.focus = function(){
clearTimeout(wait);
wait = setTimeout(callback, millisecond);
};
}
</script>
</head>
<body onload="onInactive(5000, logout);"></body>
</html>
UPDATE
I updated my solution as #Tom suggestion.
<!DOCTYPE html>
<html ng-app="Application_TimeOut">
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
</head>
<body>
</body>
<script>
var app = angular.module('Application_TimeOut', []);
app.run(function($rootScope, $timeout, $document) {
console.log('starting run');
// Timeout timer value
var TimeOutTimerValue = 5000;
// Start a timeout
var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
var bodyElement = angular.element($document);
angular.forEach(['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus'],
function(EventName) {
bodyElement.bind(EventName, function (e) { TimeOut_Resetter(e) });
});
function LogoutByTimer(){
console.log('Logout');
///////////////////////////////////////////////////
/// redirect to another page(eg. Login.html) here
///////////////////////////////////////////////////
}
function TimeOut_Resetter(e){
console.log(' ' + e);
/// Stop the pending timeout
$timeout.cancel(TimeOut_Thread);
/// Reset the timeout
TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
}
})
</script>
</html>
Click here to see at Plunker for updated version

There should be different ways to do it and each approach should fit a particular application better than another. For most apps, you can simply just handle key or mouse events and enable/disable a logout timer appropriately. That said, on the top of my head, a "fancy" AngularJS-y solution is monitoring the digest loop, if none has been triggered for the last [specified duration] then logout. Something like this.
app.run(function($rootScope) {
var lastDigestRun = new Date();
$rootScope.$watch(function detectIdle() {
var now = new Date();
if (now - lastDigestRun > 10*60*60) {
// logout here, like delete cookie, navigate to login ...
}
lastDigestRun = now;
});
});

Played with Boo's approach, however don't like the fact that user got kicked off only once another digest is run, which means user stays logged in until he tries to do something within the page, and then immediatelly kicked off.
I am trying to force the logoff using interval which checks every minute if last action time was more than 30 minutes ago. I hooked it on $routeChangeStart, but could be also hooked on $rootScope.$watch as in Boo's example.
app.run(function($rootScope, $location, $interval) {
var lastDigestRun = Date.now();
var idleCheck = $interval(function() {
var now = Date.now();
if (now - lastDigestRun > 30*60*1000) {
// logout
}
}, 60*1000);
$rootScope.$on('$routeChangeStart', function(evt) {
lastDigestRun = Date.now();
});
});

You could also accomplish using angular-activity-monitor in a more straight forward way than injecting multiple providers and it uses setInterval() (vs. angular's $interval) to avoid manually triggering a digest loop (which is important to prevent keeping items alive unintentionally).
Ultimately, you just subscribe to a few events that determine when a user is inactive or becoming close. So if you wanted to log out a user after 10 minutes of inactivity, you could use the following snippet:
angular.module('myModule', ['ActivityMonitor']);
MyController.$inject = ['ActivityMonitor'];
function MyController(ActivityMonitor) {
// how long (in seconds) until user is considered inactive
ActivityMonitor.options.inactive = 600;
ActivityMonitor.on('inactive', function() {
// user is considered inactive, logout etc.
});
ActivityMonitor.on('keepAlive', function() {
// items to keep alive in the background while user is active
});
ActivityMonitor.on('warning', function() {
// alert user when they're nearing inactivity
});
}

I tried out Buu's approach and couldn't get it quite right due to the sheer number of events that trigger the digester to execute, including $interval and $timeout functions executing. This leaves the application in a state where it never be idle regardless of user input.
If you actually need to track user idle time I am not sure that there is a good angular approach. I would suggest that a better approach is represented by Witoldz here https://github.com/witoldsz/angular-http-auth. This approach will prompt the user to reauthenticate when an action is taken that requires their credentials. After the user has authenticated the previous failed request is reprocessed and the application continues on as if nothing happened.
This handles the concern that you might have of letting the user's session expire while they are active since even if their authentication expires they are still able to retain the application state and not lose any work.
If you have some kind of session on your client (cookies, tokens, etc) you could watch them as well and trigger your logout process if they expire.
app.run(['$interval', function($interval) {
$interval(function() {
if (/* session still exists */) {
} else {
// log out of client
}
}, 1000);
}]);
UPDATE: Here is a plunk that demonstrates the concern. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W.
What this demonstates is that the digester run time is updated only when the interval ticks. Once the interval reaches it max count then the digester will no longer run.

ng-Idle looks like the way to go, but I could not figure out Brian F's modifications and wanted to timeout for a sleeping session too, also I had a pretty simple use case in mind. I pared it down to the code below. It hooks events to reset a timeout flag (lazily placed in $rootScope). It only detects the timeout has happened when the user returns (and triggers an event) but that's good enough for me. I could not get angular's $location to work here but again, using document.location.href gets the job done.
I stuck this in my app.js after the .config has run.
app.run(function($rootScope,$document)
{
var d = new Date();
var n = d.getTime(); //n in ms
$rootScope.idleEndTime = n+(20*60*1000); //set end time to 20 min from now
$document.find('body').on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkAndResetIdle); //monitor events
function checkAndResetIdle() //user did something
{
var d = new Date();
var n = d.getTime(); //n in ms
if (n>$rootScope.idleEndTime)
{
$document.find('body').off('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart'); //un-monitor events
//$location.search('IntendedURL',$location.absUrl()).path('/login'); //terminate by sending to login page
document.location.href = 'https://whatever.com/myapp/#/login';
alert('Session ended due to inactivity');
}
else
{
$rootScope.idleEndTime = n+(20*60*1000); //reset end time
}
}
});

I think Buu's digest cycle watch is genius. Thanks for sharing. As others have noted $interval also causes the digest cycle to run. We could for the purpose of auto logging the user out use setInterval which will not cause a digest loop.
app.run(function($rootScope) {
var lastDigestRun = new Date();
setInterval(function () {
var now = Date.now();
if (now - lastDigestRun > 10 * 60 * 1000) {
//logout
}
}, 60 * 1000);
$rootScope.$watch(function() {
lastDigestRun = new Date();
});
});

I have used ng-idle for this and added a little logout and token null code and it is working fine, you can try this.
Thanks #HackedByChinese for making such a nice module.
In IdleTimeout i have just deleted my session data and token.
Here is my code
$scope.$on('IdleTimeout', function () {
closeModals();
delete $window.sessionStorage.token;
$state.go("login");
$scope.timedout = $uibModal.open({
templateUrl: 'timedout-dialog.html',
windowClass: 'modal-danger'
});
});

I would like to expand the answers to whoever might be using this in a bigger project, you could accidentally attach multiple event handlers and the program would behave weirdly.
To get rid of that, I used a singleton function exposed by a factory, from which you would call inactivityTimeoutFactory.switchTimeoutOn() and inactivityTimeoutFactory.switchTimeoutOff() in your angular application to respectively activate and deactivate the logout due to inactivity functionality.
This way you make sure you are only running a single instance of the event handlers, no matter how many times you try to activate the timeout procedure, making it easier to use in applications where the user might login from different routes.
Here is my code:
'use strict';
angular.module('YOURMODULENAME')
.factory('inactivityTimeoutFactory', inactivityTimeoutFactory);
inactivityTimeoutFactory.$inject = ['$document', '$timeout', '$state'];
function inactivityTimeoutFactory($document, $timeout, $state) {
function InactivityTimeout () {
// singleton
if (InactivityTimeout.prototype._singletonInstance) {
return InactivityTimeout.prototype._singletonInstance;
}
InactivityTimeout.prototype._singletonInstance = this;
// Timeout timer value
const timeToLogoutMs = 15*1000*60; //15 minutes
const timeToWarnMs = 13*1000*60; //13 minutes
// variables
let warningTimer;
let timeoutTimer;
let isRunning;
function switchOn () {
if (!isRunning) {
switchEventHandlers("on");
startTimeout();
isRunning = true;
}
}
function switchOff() {
switchEventHandlers("off");
cancelTimersAndCloseMessages();
isRunning = false;
}
function resetTimeout() {
cancelTimersAndCloseMessages();
// reset timeout threads
startTimeout();
}
function cancelTimersAndCloseMessages () {
// stop any pending timeout
$timeout.cancel(timeoutTimer);
$timeout.cancel(warningTimer);
// remember to close any messages
}
function startTimeout () {
warningTimer = $timeout(processWarning, timeToWarnMs);
timeoutTimer = $timeout(processLogout, timeToLogoutMs);
}
function processWarning() {
// show warning using popup modules, toasters etc...
}
function processLogout() {
// go to logout page. The state might differ from project to project
$state.go('authentication.logout');
}
function switchEventHandlers(toNewStatus) {
const body = angular.element($document);
const trackedEventsList = [
'keydown',
'keyup',
'click',
'mousemove',
'DOMMouseScroll',
'mousewheel',
'mousedown',
'touchstart',
'touchmove',
'scroll',
'focus'
];
trackedEventsList.forEach((eventName) => {
if (toNewStatus === 'off') {
body.off(eventName, resetTimeout);
} else if (toNewStatus === 'on') {
body.on(eventName, resetTimeout);
}
});
}
// expose switch methods
this.switchOff = switchOff;
this.switchOn = switchOn;
}
return {
switchTimeoutOn () {
(new InactivityTimeout()).switchOn();
},
switchTimeoutOff () {
(new InactivityTimeout()).switchOff();
}
};
}

[add below script in application reference js file ][1]
[1]: https://rawgit.com/hackedbychinese/ng-idle/master/angular-idle.js
var mainApp = angular.module('mainApp', ['ngIdle']);
mainApp.config(function (IdleProvider, KeepaliveProvider) {
IdleProvider.idle(10*60); // 10 minutes idel user
IdleProvider.timeout(5);
KeepaliveProvider.interval(10);
});
mainApp
.controller('mainController', ['$scope', 'Idle', 'Keepalive', function ($scope,
Idle, Keepalive) {
//when login then call below function
Idle.watch();
$scope.$on('IdleTimeout', function () {
$scope.LogOut();
//Logout function or redirect to logout url
});
});

Related

Refresh of scope after remote change to data

In my controller for a mpbile app based on Angular1 is have (for example) the following function:
var getItem = function() {
// Initialize $scope
$scope.url = "(url to get my data)";
$http.get($scope.url).success(function(data) {
$scope.itemDetails = data; // get data from json
});
};
getItem();
and this works just fine.. with one problem.. it doesnt update. Even if I switch pages and come back, if the scope hasnt changed, it doesnt reflect new data in the scope.
So, i built in an $interval refresh to look for changes in the scope, this works fine EXCEPT, when i leave the page to go to another, that interval keeps polling. This is obviously a bad idea in a mobile app where data and battery usage may be an issue.
So.. how can I keep checking the scope for 'live changes' when ON that page only OR what is best practice for the scope to refresh on data changes.
I have read about digests and apply but these still seem to be interval checks which I suspect will keep operation after switching pages.
Or on angular apps with live data, is constantly polling the API the 'thing to do' (admittedly the data the page pulls is only 629 bytes, but i have a few pages to keep live data on, so it will add up)
Thanks
When you create a controller, the function's in it are declared, but not run. and since at the end of the controller you are calling getItem(); it is run once.
Moving to another page, and coming back is not going to refresh it.
The only way to refresh is to call that function again, In your HTML or JS.
For example:
<button ng-click="getItem()">Refresh</button>
Really nice question, I have been wondering the same thing, so I checked a lot of related SO posts and wrote kind of a function that can be used.
Note: I am testing the function with a simple console.log(), please insert your function logic and check.
The concept is
$interval is used to repeatedly run the function($scope.getItem) for a period (in the below example for 1 second), A timeout is also actively running to watch for inactive time, this parameter is defined by timeoutValue (in the example its set to 5 seconds), the document is being watched for multiple events, when any event is triggered, the timeout is reset, if the timeoutValue time is exceeded without any events in the document another function is called where the interval is stopped. then on any event in the document after this, the interval is started back again.
var myModule = angular.module('myapp',[]);
myModule.controller("TextController", function($scope, $interval, $document, $timeout){
//function to call
$scope.getItem = function() {
console.log("function");
};
//main function
//functionName - specify the function that needs to be repeated for the intervalTime
//intervalTime - the value is in milliseconds, the functionName is continuously repeated for this time.
//timeoutValue - the value is in milliseconds, when this value is exceeded the function given in functionName is stopped
monitorTimeout($scope.getItem, 1000 ,5000);
function monitorTimeout(functionName, intervalTime, timeoutValue){
//initialization parameters
timeoutValue = timeoutValue || 5000;
intervalTime = intervalTime || 1000;
// Start a timeout
var TimeOut_Thread = $timeout(function(){ TimerExpired() } , timeoutValue);
var bodyElement = angular.element($document);
/// Keyboard Events
bodyElement.bind('keydown', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('keyup', function (e) { TimeOut_Resetter(e) });
/// Mouse Events
bodyElement.bind('click', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousemove', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('DOMMouseScroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousewheel', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousedown', function (e) { TimeOut_Resetter(e) });
/// Touch Events
bodyElement.bind('touchstart', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('touchmove', function (e) { TimeOut_Resetter(e) });
/// Common Events
bodyElement.bind('scroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('focus', function (e) { TimeOut_Resetter(e) });
function TimerExpired(){
if(theInterval) {
$interval.cancel(theInterval);
theInterval = undefined;
}
}
function TimeOut_Resetter(e){
if(!theInterval){
theInterval = $interval(function(){
functionName();
}.bind(this), intervalTime);
}
/// Stop the pending timeout
$timeout.cancel(TimeOut_Thread);
/// Reset the timeout
TimeOut_Thread = $timeout(function(){ TimerExpired() } , timeoutValue);
}
var theInterval = $interval(function(){
functionName();
}.bind(this), intervalTime);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myapp">
<div ng-controller="TextController">
</div>
</div>
Depending on the router you are using, you have to tell the controller to reload when the route changed or updated, because the function you pass when declaring a controller is only a factory, and once the controller is constructed it won't run again because the router caches it (unless you tell angularjs to do so, which is rarely a good idea).
So your best bet is to use the router to reload the state when the route changes. You can do this using the router event change and update that is broadcast in the scope.
If you are using angularjs' router (a.k.a., ngRoute):
$scope.$on('$routeChangeUpdate', getItem);
$scope.$on('$routeChangeSuccess', getItem);
If you are using ui.router:
$scope.$on('$stateChangeUpdate', getItem);
$scope.$on('$stateChangeSuccess', getItem);
Note: in ui.router you can add cache: false on the state declaration and it'll prevent the controller and the view to be cached.

Wait for event to happen in server side in angular JS

I am really new to angularJS. I need to develop a page where angular JS wait for a event to happen at server side so angular JS should keep checking server using $http call in every 2 seconds. Once that event completes Angular should not invoke any $http call to server again.
I tried different method but it gives me error like "Watchers fired in the last 5 iterations: []"
Please let me know how to do it.
Following is my code
HTML
<div ng-controller="myController">
<div id="divOnTop" ng-show="!isEventDone()">
<div class="render"></div>
</div>
</div>
Angular JS
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http) {
$scope.ready = false;
$scope.isEventDone = function () {
$scope.ready = $scope.getData();
return $scope.ready;
};
$scope.getData = function () {
if (! $scope.ready) {
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
});
}
};
setInterval($scope.isPageReady, 5000);
});
A few things here.
I'm not convinced the accepted answer actually works nor solves the initial problem. So, I'll share my 2 cents here.
$scope.ready = $scope.getData(); will set $scope.ready to undefined each time since this method doesn't return anything. Thus, ng-show="!isEventDone()" will always show the DOM.
You should use angular's $interval instead of setInterval for short-polling in angular.
Also, I've refactored some redundancy.
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $interval) {
var intervalPromise = $interval($scope.getData, 5000);
$scope.getData = function () {
if (! $scope.isEventDone) {
$http
.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
if($scope.isEventDone) {
$interval.cancel(intervalPromise);
}
});
}
else {
$interval.cancel(intervalPromise);
}
};
});
This should work and solve your initial problem. However, there's a scenario where your server may be on a high load and takes 3 seconds to respond. In this case, you're calling the server every 2 seconds because you're waiting for 5 seconds after the previous request has started and not waiting for after the previous request has ended.
A better solution than this is to use a module like async which easily handles asynchronous methods. Combining with $timeout:
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $timeout) {
var getData = function(cb){
if(!$scope.isEventDone) return cb();
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
cb();
});
};
// do during will run getData at least once
async.doDuring(getData, function test(err, cb) {
// asynchronous test method to see if loop should still occur
// call callback 5 seconds after getData has responded
// instead of counting 5 seconds after getData initiated the request
$timeout(function(){
cb(null, !$scope.isEventDone);
// if second param is true, call `getData()` again otherwise, end the loop
}, 5000);
}, function(err) {
console.log(err);
// if you're here, either error has occurred or
// the loop has ended with `$scope.isEventDone = true`
});
});
This will call the timeout after the request has ended.
A better alternative, if you have control of the server, is to use a websocket which will enable long-polling (server notifies the client instead of client making frequent requests) and this will not increase significant load on the server as clients grow.
I hope this helps
In your example $scope.pageIsReady does not exist. What you could do is inject the $timeout service into your controller and wrap your http call inside of it:
var timeoutInstance = $timeout(function(){
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
if($scope.ready){
$timeout.cancel(timeoutInstance);
else
$scope.getData();
}
});
},5000);
cancel will stop the timeout from being called. I have not tested this but it should be along those lines.
Also not sure what type of backend you are using but if it is .net you could look into SignalR which uses sockets so the server side tells the front end when it is ready and therefore you no longer need to use polling.

Constantly running service while in app

I am developing an app that pushes user's geolocation to the server (with regards to permissions, of course).Currently, when the user navigates to the account page, a method is invoked to push their geolocation to the server.Instead, I would like a service (if that's the best method) to run while the app is running that will push the geolocation. This means that the location held for the user will always be up to date and not just updated when the account page is visited.
There is a checkbox on the account page where they select if they want to share their geolocation or not.I have a $watcher on the checkbox...
$scope.$watch("account.shareLocation", function(newValue, oldValue) {
if (newValue) {
locationService.pushLocation(userPosition);
} else {
locationService.pushLocation(null);
}
});
So, if the user selects to share their location (newValue === true) then, pushLocation() should be constantly invoked with the userPosition until the value of account.shareLocation (the checkbox) is altered otherwise.
I made a small example here: https://jsfiddle.net/ojzdxpt1/2/
You have your main controller that inits the service if the user has allowed it (I also added a mock $timeout of them unchecking it).
var app = angular.module('TestApp', []);
app.controller('TestController', function($scope, $timeout, LocationService) {
console.log('app init');
//-- on checkbox change and/or app init;
var trackLocation = true;
if (trackLocation) {
LocationService.start();
//-- imitate them turning the service off
$timeout(function() {
console.log('stop tracking location');
LocationService.stop();
},10000);
}
});
Now for your service you could do something like this:
app.service('LocationService', function($interval) {
var int;
return {
start: function() {
int = $interval(this.saveLocation,3000);
},
stop: function() {
$interval.cancel(int);
},
saveLocation: function() {
console.log('save location');
}
}
});

afterEach not waiting to complete before continuing

I use afterEach to logout, but my tests try to login before afterEach completes logging out.
I'm trying to avoid the use of sleep statements (really slows things down).
How can I get a test to wait for the previous beforeEach to finish?
code snippets:
afterEach(function() {
AccountBlock.logout().then(function(){
browser.sleep(2000); // I want to get rid of this
});
});
the logout() method (from the AccountBlock object):
this.logout = function() {
var logoutLink = this.logoutLink;
var userName = this.userName;
return logoutLink.isDisplayed().then(function(isDisplayed){
if (!isDisplayed) {
return userName.click().then (function() {
return logoutLink.click();
}); // open the account menu before logout
} else { // account menu already displayed
return logoutLink.click();
}
});
}
this.userName = element(by.css('a[ng-bind="userName"]'));
this.logoutLink = element(by.css('a[ng-click^="logout"]'));
browser.wait is definitely the way to go but rather than calling on elements directly to ask if they are present or displayed protractor has built-in functionality for just this called ExpectedConditions. You can use this let protractor force the browser to wait until it has met it's own expected conditions.
Below I am finding the element, using expected conditions to detect the presence of the element then using the not operator to detect when the element is no longer present. This should force browser.wait to pause future promises until that condition is true.
afterEach(function(){
var ec = protractor.ExpectedConditions;
var logout = element(by.css('a[ng-click^="logout"]'));
var logoutLink = ec.presenceOf(logout);
var logoutLinkNotThereAnymore = ec.not(logoutLink);
//do logout
browser.wait(logoutLinkNotThereAnymore, 10000, "Waited 10 seconds");
});
beforeEach(function(){
//do login
});
I use browser.wait() to avoid sleep(), in your login block of code you can do this
var doLogin = function(){
browser.wait(element(by.id('yourLoginBoxId')).isDisplayed); //this will wait until the html with the form to login is in your DOM
//your code to login here
};
note that isDisplayed doesn't have the ().

Cordova/Phonegap + Angular: promise not resolving until next digest cycle

I'm using Angular + JQMobile inside a Cordova mobile app, and I'm having some difficulty with promises. I have a cordovaReady service that returns a promise and I'm looking to hide a splashscreen (no lectures please) when the promise is resolved. The trick comes when I try to use an if-then condition to resolve the promises at different times. My code:
app.factory('cordovaReady', function($q) {
var deferred = $q.defer();
return {
ready: function() {
document.addEventListener(
"deviceready",
function () {
deferred.resolve();
},
false
);
return deferred.promise;
}
}
});
app.run(['$rootScope', '$location', 'TagTemplates', 'cordovaReady', function($scope, $location, TagTemplates, cordovaReady) {
// If there is a user logged in, --fire camera-- and go to tag template
if ($scope.currentUser) {
// fire camera here, make sure cordova is ready first.
// alert('found user');
var promise = cordovaReady.ready();
promise.then(function() {
// alert('about to capture');
console.log('firing capture');
$scope.captureImage();
navigator.splashscreen.hide(); // this one fires.
});
console.log($scope.currentUser);
$scope.templates = $scope.currentUser.attributes.templates[0];
$scope.$apply();
$location.url("/#main");
} else {
var promise = cordovaReady.ready();
promise.then(function() {
alert('no user logged in, hide splashscreen');
navigator.splashscreen.hide(); // this one does not.
$scope.$apply(); // even with this.
});
}
}
In the case of an existing user, the splashscreen is hidden as desired, I thought because of scope updates that happen within this if statement. However, in the event of no existing user, the splashscreen does not fire until the next digest cycle (clicking on a button or link in my app). I verified this with the alert.
I've tried all manner of scope apply, include variants of setTimeout with 0 delay, etc.
Any thoughts?
Thanks.

Resources