I am trying show a timeout error message after particular interval of idle time(say 10 mins)
I followed the below link and it is working as expected.
https://long2know.com/2015/04/user-session-timeout/
var idleService = function ($rootScope, $timeout, $log) {
var idleTimer = null,
startTimer = function () {
$log.log('Starting timer');
idleTimer = $timeout(timerExpiring, 10000);
},
stopTimer = function () {
if (idleTimer) {
$timeout.cancel(idleTimer);
}
},
resetTimer = function () {
stopTimer();
startTimer();
},
timerExpiring = function () {
stopTimer();
$rootScope.$broadcast('sessionExpiring');
$log.log('Timer expiring ..');
};
startTimer();
return {
startTimer: startTimer,
stopTimer: stopTimer,
resetTimer: resetTimer
};
};
Is it possible to achieve it without the $broadcast and $on. Any guidance on how to make the controller variable know when the timeout is expired, currently it is achieved via $broadcast from service and $on on controller and then the variable will be modified in $on function.
You could use RxJS to create an observable. Your controller would subscribe to it through your service and when the timeout is expired, a value is emitted on your observable (could be a void value). This way, every controller that has a subscription on that observable would receive the signal that the timeout is expired.
I did a plunker to illustrate this: https://plnkr.co/edit/JSgKWLwQLAhqgRGPn9ys
First, you start by creating a method that returns an observable. I'm using a Subject as you don't need to send an initial value or a value that has already been emitted:
var getExpiredSignal = function() {
return subject.asObservable();
}
Now, when you timeout is expired, you need to send a signal through the observable by doing:
subject.next();
As your controller did subscribe to this observable via the line:
timeout.getExpiredSignal().subscribe(() => console.log('Expired!'));
The callback function logging "Expired" is triggered.
I made it work with the below approach
var idleService = function ($rootScope, $timeout, $log) {
var idleTimer = null,
startTimer = function () {
$log.log('Starting timer');
idleTimer = $timeout(timerExpiring, 10000);
},
stopTimer = function () {
if (idleTimer) {
$timeout.cancel(idleTimer);
}
},
resetTimer = function () {
stopTimer();
startTimer();
},
timerExpiring = function () {
stopTimer();
$rootScope.sessionFlag = true;
$log.log('Timer expiring ..');
};
startTimer();
return {
startTimer: startTimer,
stopTimer: stopTimer,
resetTimer: resetTimer
};
};
Now I am not using $broadcast and $on but I am changing the $rootscope.sessionFlag which I am referencing in my html
Related
In the below given code, we are calling startTimer function , where we use $interval to trigger the request to backend until we get the data.status == "complete" ;and once status is completed ,we set the flag = true and flag will lead to trigger the watch and it calls the $scope.stop function to cancel the timer using $interval.cancel.
But here issue arises i.e. , $interval.cancel doesn't know which timer to stop first.
When there are multiple request to call the timer based on id, the completed timer based on that id should get cancelled.
So my question is How to cancel the timer based on the id.
angular.module('timerApp', ['timerApp.controllers']);
angular.module('timerApp.controllers', []).controller('timerController', ['$scope', '$interval',
function($scope, $interval) {
var timer;
var time = 10;
$scope.countdown = time;
$scope.startTimer = function(id) {
timer = $interval(function(id) {
$scope.countdown--;
//res is response from my backend
someRestService(id).then(res);
var data = res;
if (data.status = "complete") {
$scope.timerFlag = true;
}
}, 15000);
};
}
$scope.stopTimer = function() {
$interval.cancel(timer);
};
$scope.$watch() {
if ($scope.timerFlag == true) {
$scope.stopTimer();
}
}
]);
scenario :
RestapiHit/627
RestapiHit/628
RestapiHit/629
it will call the 627 request and finish the process and then when about to cancel using $interval.cancel it goes for latest one and cancel the 629 but not 627
Take a look at:
angular.module('timerApp', ['timerApp.controllers']);
angular.module('timerApp.controllers', []).controller('timerController', ['$scope', '$interval',
function($scope, $interval) {
var timer= {} ;
var time = 10;
$scope.countdown = time;
$scope.startTimer = function(id) {
timer[id] = $interval(function(id) {
$scope.countdown--;
//res is response from my backend
someRestService(id).then(res);
var data = res;
if (data.status = "complete") {
$scope.stopTimer(id);
}
}, 15000);
};
}
$scope.stopTimer = function(id) {
$interval.cancel(timer[id]);
delete timer[id];
};
]);
Right now, you just have one timer value assigned as it is declared at controller level. But since, you might have multiple calls for $interval so i think this approach should work better.
I'm learning how to use angulars $interval by building a simple app that generates random string every second (inside a service) and returns it to the controller. However, I cant get the data. I am able to console.log it inside the function call, but not in the calling function inside the controller. What am I missing? I read here, but still dont get the point.
The controller:
angular.module("app").controller("authorC", function ($scope, WebService) {
$scope.generate = function () {
console.log("generating..");
WebService.generateRandom().then(function (y) {
console.log(y);
});
};
$scope.stop = function () {
WebService.stopGen();
};
});
The service:
angular.module("app").service("WebService", function ($http, $interval) {
function makeid() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 5; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
var genInterval;
this.generateRandom = function () {
genInterval = $interval(function () {
return makeid();
}, 1000);
return genInterval;
};
this.stopGen = function () {
$interval.cancel(genInterval);
console.log("Stopped");
}
});
$interval returns a promise that will get notified on each iteration, resolved when the timer finished to run (interval is completed) or rejected if the interval was cancelled.
https://docs.angularjs.org/api/ng/service/$interval
You can't use it to get a return value of the function that the interval runs. I'm not sure what you are trying to achieve, but you can for example save the randomValue on your service (and replace it on each iteration), so it will be able to access it from outside.
I use this code to safe work with $timeout :
$scope.intervalFunction = function () {
setTimeout(function () {
//DO
console.log('refresh');
if ($scope.currentNode) {
$scope.intervalFunction();
}
}, 5000)
};
function setTimeout(fn, delay) {
var promise = $timeout(fn, delay);
var deregister = $scope.$on('$destroy', function () {
$timeout.cancel(promise);
});
promise.then(deregister);
}
$scope.intervalFunction();
it is correct?
There are a few optimizations here, if it is always the same function that needs to be executed in the timeout:
var refreshTimeout;
$scope.intervalFunction = function () {
// Assign to refreshTimeout, so it can be cancelled on the destroy of the scope
refreshTimeout = $timeout(function() {
console.log('refresh');
if ($scope.currentNode) {
$scope.intervalFunction();
}
}, 5000)
};
$scope.intervalFunction();
// Only one timeout to destroy
// Though I don't think this is even necessary, because probably
// the timeout gets cancelled anyway on the destruction of the scope
$scope.$on('$destroy', function () {
if (refreshTimeout)
refreshTimeout.cancel();
});
EDIT
According to this article, you do need to destroy the timeout yourself :)
I have an angular service class : -
angular.module('triggerTips')
.service('userData', function ($rootScope, $http, $log, $firebase) {
this._log = {
service : 'userData'
};
// Synchronized objects storing the user data
var config;
var userState;
// Loads the user data from firebase
this.init = function(readyCallback) {
var log = angular.extend({}, this._log);
log.funct = 'init';
var fireRef = new Firebase('https://XYZfirebaseio.com/' + $rootScope.clientName);
config = $firebase(fireRef.child('config')).$asObject();
userState = $firebase(fireRef.child('userState').child($rootScope.userName)).$asObject();
Promise.all([config.$loaded(), userState.$loaded()]).
then(
function() {
if(config == null || Object.keys(config).length < 4) {
log.message = 'Invalid config';
$log.error(log);
return;
}
if(!userState.userProperties) {
userState.userProperties = {};
}
if(!userState.contentProperties) {
userState.contentProperties = {};
}
log.message = 'User Properties: ' + JSON.stringify(userState.userProperties);
$log.debug(log);
log.message = 'Content Properties: ' + JSON.stringify(userState.contentProperties);
$log.debug(log);
log.message = 'Loaded user data from firebase';
$log.debug(log);
readyCallback();
},
function() {
log.message = 'Unable to load user data from firebase';
$log.error(log);
}
);
};
// Returns the initial tip configuration
this.getConfig = function() {
return config;
};
// Set the value of a user property
// A user property is something about the user himself
this.setUserProperty = function(property, value) {
if(!userState.userProperties) {
userState.userProperties = {};
}
userState.userProperties[property] = value;
userState.$save();
$rootScope.$broadcast('user-property-change', property);
};
// Get the value of a user property
this.getUserProperty = function(property) {
if(userState.userProperties) {
return userState.userProperties[property];
}
};
// Set the value of a user content property
// A content property is something about a particular peice of content for a particular user
this.setContentProperty = function(contentName, property, value) {
if(!userState.contentProperties[contentName]) {
userState.contentProperties[contentName] = {};
}
userState.contentProperties[contentName][property] = value;
userState.$save();
$rootScope.$broadcast('content-property-change', contentName, property);
};
// Increment a count property on the user state for a given tip
this.incrementContentProperty = function(contentName, property) {
if(!userState.contentProperties[contentName]) {
userState.contentProperties[contentName] = {};
}
if(!userState.contentProperties[contentName][property]) {
userState.contentProperties[contentName][property] = 0;
}
userState.contentProperties[contentName][property]++;
userState.$save();
$rootScope.$broadcast('content-property-change', contentName, property);
};
// Returns the user state for a given tip and property
this.getContentProperty = function(contentName, property) {
if(userState.contentProperties) {
var t = userState.contentProperties[contentName];
if(t) {
return t[property];
}
}
};
});
I am trying to unit test this service using jasmine:-
my unit test is :-
'use strict';
describe('Service: userData', function () {
// load the service's module
beforeEach(function() {
module('triggerTips');
});
// instantiate service
var userData;
beforeEach(inject(function (_userData_) {
userData = _userData_;
}));
it('should load correctly', function () {
expect(!!userData).toBe(true);
});
describe('after being initialized', function () {
beforeEach(function(done) {
// Unable to get this working because the callback is never called
userData.init(function() {
done();
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
});
it('should have a valid config', function (done) {
setTimeout(function() {
expect(Object.keys(userData.getConfig()).length == 0);
done();
}, 1500);}); }); });
I read about the Asynchronous Support in Jasmine, but as I am rather new to unit testing with JavaScript couldn't make it work.
I am receiving an error :
Async callback was not invoked within timeout specified by
jasmine.DEFAULT_TIMEOUT_INTERVAL
Can somebody help me providing working example of my code with some explanation?
I would suggest that you replace setTimeout with $timeout so as to speed up your spec suite. You will need ngMock to be a part of your spec suite in order to get this working the intended way, but that seems to have already been taken care of looking at your spec. Good stuff.
Then in order to make the async nature of the spec "go away" you would call:
$timeout.flush([delay]) where delay is optional.
If no delay is passed, all the pending async tasks (inside the angular world) will finish what they're doing.
If a delay is passed, all pending tasks within the specified delay will finish. Those outside of the specified delay will remain in a 'pending' state.
With this, you can remove the done callback and write your tests as such:
describe('after being initialized', function () {
var $timeout;
beforeEach(function () {
// Unable to get this working because the callback is never called
userData.init();
inject(function ($injector) {
$timeout = $injector.get('$timeout');
});
}));
it('should have a valid config', function () {
$timeout.flush();
// callback should've been called now that we flushed().
expect(Object.keys(userData.getConfig()).length).toEqual(0);
});
});
What Promise implementation are you using? I see a call to Promise.all but for the sake of continuing on with my answer I'm going to assume it is equivalent to $q.all. Running $timeout.flush should take care of resolving those values.
If you want to write expectations on the rejected/resolved values of a promise in Jasmine, I would look into something like jasmine-promise-matchers to make it clean and pretty, but barring that you could do something like this:
// $q
function get () {
var p1 = $timeout(function () { return 'x'; }, 250);
var p2 = $timeout(function () { return 'y'; }, 2500);
return $q.all([p1, p2]);
}
// expectation
it('is correct', function () {
var res;
get().then(function (r) {
res = r;
});
$timeout.flush(2500);
expect(res).toEqual(['x', 'y']);
});
Depending on your setup, you may or may not have to stub out/spy on (depending on your frameworks definition of a spy) the promise in relation to your local config variable, but that's another story altogether I reckon.
I am not at all familiar with $firebase(something).$asObject.$loaded - as such I may have missed something here, but assuming it works 'just like any other promise' you should be good to go.
jsfiddle
I am using AngularJS and ngProgress to display a YouTube-like loading bar at the top of my site.
The bar is started, then new data is loaded in via ajax, and once the request is finished, the bar is completed.
Example:
var TestCtrl = function( $scope, $location, Tests, ngProgress )
{
// start progressbar
ngProgress.start();
$scope.tests = Tests.query(
function()
{
// end progressbar
ngProgress.complete()
}
);
};
Now my question is: How can I integrate this principle higher up in the order of things, such that I don't have to repeat the code for every single controller?
You could use a service which controls ngProgress (acting like a wrapper over it) and listen for changes in the url.
Each time the url changes the event $locationChangeSuccess is broadcasted (more info at $location) which we could listen to invoke ngProgress.start()
However we don't know when it's completed (we can't have a bar on the top loading forever), therefore we need to call ngProgress.complete() explicitly in our controllers OR we could assume that our async functions might take like 5 seconds to be completed and call ngProgress.complete() using a timer in our wrapper service
When the loading bar is already visible and there's a change in the url we need to reset the status of the bar by calling ngProgress.reset()
You can use the following approach to solve these problems:
angular.module('myApp').factory('Progress', function (ngProgress) {
var timer;
return {
start: function () {
var me = this;
// reset the status of the progress bar
me.reset();
// if the `complete` method is not called
// complete the progress of the bar after 5 seconds
timer = setTimeout(function () {
me.complete();
}, 5000);
},
complete: function () {
ngProgress.complete();
if (timer) {
// remove the 5 second timer
clearTimeout(timer);
timer = null;
}
},
reset: function () {
if (timer) {
// remove the 5 second timer
clearTimeout(timer);
// reset the progress bar
ngProgress.reset();
}
// start the progress bar
ngProgress.start();
}
};
});
To listen for changes in the url and show the progress bar we could use:
angular.module('myApp')
.run(function (Progress) {
$rootScope.$on('$locationChangeSuccess', function () {
Progress.start();
});
}
Now we can manually control the completeness of the status bar by injecting the Progress service and calling the method Progress.complete() when all of our async functions have finished (we could also control this from any service that makes async calls):
angular.module('myApp')
.controller('SomeCtrl', function (Progress) {
setTimeout(function () {
Progress.complete();
}, 2000);
});
Here is an example using Interceptor:
.factory('interceptorNgProgress', [
'ngProgressFactory', function (ngProgressFactory) {
var complete_progress, getNgProgress, ng_progress, working;
ng_progress = null;
working = false;
getNgProgress = function() {
if(!ng_progress) {
ng_progress = ngProgressFactory.createInstance();
return ng_progress;
}
return ng_progress;
};
complete_progress = function() {
var ngProgress;
if (working) {
ngProgress = getNgProgress();
ngProgress.complete();
return working = false;
}
};
return {
request: function(request) {
var ngProgress;
ngProgress = getNgProgress();
if (request.url.indexOf('.html') > 0) {
return request;
}
if (!working) {
ngProgress.reset();
ngProgress.start();
working = true;
}
return request;
},
requestError: function(request) {
complete_progress();
return request;
},
response: function(response) {
complete_progress();
return response;
},
responseError: function(response) {
complete_progress();
return response;
}
}
}])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('interceptorNgProgress');
});
I would put it in an angular directive and then you can pass that into any controller you want to be able to use it.
http://docs.angularjs.org/guide/directive
Edit, thinking about it a service mght be better for this case.
http://docs.angularjs.org/guide/dev_guide.services.creating_services
You apparently already have a Tests service. Override it so ngProgress is injected into it and have Tests always call ngProgress.start() and ngProgress.complete() for you in query.