Intentional delaying in Angularjs - angularjs

I am making a little diagnose web page and want to make it look like the system is working hard in background by showing loading image for a while.
I am not a programmer. so I basically look all over for a chunk of codes that works and this is how I tried... but justDelaying function never delays the later process. It seems all the process is running simultaneously.
app.controller('HardworkCtrl', function($scope, $timeout) {
$scope.hardWork = function() {
// start showing hard-working image
$scope.loading = true;
$scope.justDelaying = function() {
$timeout(function() {
// do nothing...
}, 5000);
}
$scope.justDelaying();
$scope.theAnswer = "42."
// stop showing hard-working image
$scope.loading = false;
};
};
Any idea?

Anything that happens inside "$timeout" is asynchronous, so it's just scheduled for later, without blocking the main execution. In other words, the instructions after you call $scope.justDelaying() happen immediately, while the instructions inside justDelaying get delayed for 5 seconds. To make those instructions execute later, you need to move them inside the $timeout, like this:
app.controller('HardworkCtrl', function($scope, $timeout) {
$scope.hardWork = function() {
// start showing hard-working image
$scope.loading = true;
$scope.delayThenDoStuff = function() {
$timeout(function() {
$scope.theAnswer = "42."
// stop showing hard-working image
$scope.loading = false;
}, 5000);
}
$scope.delayThenDoStuff();
};
};

Related

Measure page load of angular app

Hi All I have tried the var loadTime = window.performance.timing.domContentLoadedEventEnd- window.performance.timing.navigationStart; but value seems to be much smaller than actual time it takes to load the page.
Ideally I want to have a stopwatch that stop counting when the page is fully loaded. Any suggestion?
I won't say that this method is not without its flaws, but if you are measuring the loading of data, here is one method you can use to achieve a loading counter.
Create a bool to evaluate against, isLoading. Hoist it to true.
Create your int, loadTime.
Use a SetInterval to increment the int
Load some data
after you have processed the data, set the isLoading condition to false.
Example:
https://plnkr.co/edit/ARVpFd9NmlFNURlyS9SZ
app.controller("myCtrl", function($scope, $http) {
$scope.isLoading = true;
$scope.loadTime = 0;
$scope.msg = '';
var tmr = setInterval(function() {
if(!$scope.isLoading || $scope.isLoading === false) {
clearInterval(tmr);
}
$scope.loadTime += 0.01;
}, 100);
$http.get('https://unsplash.com')
.then(function(res) {
$scope.msg = 'Data is loaded';
$scope.isLoading = false;
});

How to show a busy message while angular digest is running

I have a complex page (lots of ng-repeats nested) so the digest takes a while to finish. I want to give the user some feedback, so they don't think the browser is hung.
Below is a sample fiddle, when you click HIT ME the $watch hangs for 2 seconds. I want the "Working" message to show up, but it does not.
https://jsfiddle.net/jdhenckel/c7edvdt1/
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.n = 0;
$scope.test = function() {
$scope.msg = 'Working...';
$scope.n += 1;
};
$scope.$watch(function(scope) {
return $scope.n;
}, function() {
var x = Date.now() + 2000;
while (x > Date.now()) {}
$scope.msg = 'Done.';
});
});
I also tried to use JQuery to directly change the DOM before the digest, but that also didn't work. Seems like my only option is to move all the long running stuff a future digest using a $timeout, but that seems like a hack!
Is there an elegant way to notify the user that the digest is running?
EDIT: Here is a possibly more realistic example.
https://jsfiddle.net/jdhenckel/9vcLq0k3/
$scope.n = 0;
$scope.msg = 'Ready';
$scope.test = function() {
$scope.msg = 'Working...';
$timeout(function() {
doStuff();
$scope.msg = 'Done';
}, 100);
}
This works because I moved all the expensive changes into doStuff.
I was hoping that Angular would provide a simpler way to do this (such as ng-cloak for initialization.) If not, then I'll keep using $timeout.

Angular using Server Sent Events in a factory

I am trying to setup message notifications in an Angular/Rails app.
When the user logs in, I want to open a SSE connection which will subscribe to a Redis stream and push an alert to the client when the user gets a new message.
I have my SSE setup and working, but cannot figure out a way to reliably close the SSE connection when a user logs out. I am trying to build a service to handle SSE opening and closing:
angular.module('messagesApp')
.factory('StreamHandler', function(CookieHandler, MessageStream){
var StreamHandler = {
set: function(){
var user
user = CookieHandler.get();
MessageStream.get = function(){
var source = new EventSource('/api/v1/messages/count?id='+user.id)
return source
}
},
get: function(){
var source = MessageStream.get()
source.onmessage = function(event) {
//do something
}
},
kill: function(){
var source = MessageStream.get()
source.close()
}
}
return StreamHandler
})
I cannot figure out how to kill the stream that is opened in StreamHandler.set(). My attempt in the kill attribute does not work, possible because calling the getter actually creates a new stream?
I am open to other approaches: I just need a way to set and kill an EventSource stream on user login/logout.
The problem was that I was putting the function that creates an EventSource in my get attribute, instead of putting the actual EventSource object. A few changes makes it work:
.factory('StreamHandler', function(CookieHandler, MessageStream){
var StreamHandler = {
set: function(){
var user
user = CookieHandler.get();
var source = new EventSource('/api/v1/messages/count?id='+user.id)
MessageStream.get = source
},
get: function(){
var source = MessageStream.get
source.onmessage = function(event) {
console.log(event)
}
source.onerror = function(error) {
source.close()
}
},
kill: function(){
var source = MessageStream.get
source.close();
}
}
return StreamHandler
})
Look into Oboe.js - http://oboejs.com/examples
Using Oboe, I basically did (and I guess you don't need to inject $source and $http either in this case):
.factory('MyStreamingResource', ['$resource', '$http',
function($resource, $http) {
return {
stream: function(options, startFn, nodeFn, doneFn) {
oboe('//url/' + 'maybeSomeOptions/?maybe=' + options.passedAbove)
.start(startFn)
.node(options.path, nodeFn)
.done(doneFn);
}
};
}
]);
Then simply injected it and called from some controllers with:
MyStreamingResource.stream({
passedAbove: 'foo',
path: 'items.*'
},
// start callback
function(status, headers){
// console.dir(headers);
// this.abort(); // could be called from here too
},
// node callback (where your data is going to be streamed to)
function(data){
if(data !== null) {
console.dir(data);
//this.abort();
}
},
// done (if you really want to wait)
function(parsedJson){
// ...
});
Very similar to other services that you'd see with $http, but instead you have a few more callbacks to consider.
Oboe.js worked like a charm for me and the Golang go-json-rest package streaming a response (even with invalid JSON - which is quite common with streams since they aren't complete and won't have closing tags or will have extra commas, etc.). Just ensure the browser version of the script gets included on your page and you can just call it where ever. I spent a good while searching on how to do this with AngularJS, but there just didn't seem to be a straight forward facility for it. If it is at all possible.
Of course from here you could probably figure out how to make it work for your needs...Update something in the $scope, etc.

Auto logout with Angularjs based on idle user

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
});
});

AngularJS flash message clearing

I've just started using angular-flash and the messages are coming up, but where should I clear the messages?
Do I just set flash.message('') under every action or put it in the route somewhere?
Is there a way to do this on a timer so it fades out? I see in the code that there is a timeout feature but where do I initialise that?
I'm looking into the source code to try and figure out what is going on, a quick and dirty fix would be to do this when you want to clear the messages:
flash([]);
Since all of your flash messages are in an array, I assume you can handle it like any array.
The reason I'm saying it's dirty is because I'm assuming there's a better way to do this, with something like a clear() or empty() function.
I have found my answer to clearing:
flash('notice', 'User details updated!')
$location.path "/users/#{$stateParams['id']}"
$timeout (-> flash()), 2000
You can wrap it with your own service:
'use strict';
angular.module('myApp')
.service('ff', ['$rootScope', 'flash', '$timeout', function ($rootScope, flash, $timeout) {
var log = function(level) {
function inner(msg, sticky) {
if (level == 'info') flash(msg);
else flash(level, msg);
var promise = $timeout(function() {
var idx = _.findIndex($rootScope.messages, ['text', msg]);
$rootScope.messages.splice(idx, 1);
}, 2000);
if (sticky) {
$timeout.cancel(promise);
}
}
return inner;
};
return {
info: log('info'),
error: log('error'),
warn: log('warning'),
success: log('success')
};
}]);
And in the controller:
ff.info("This will go away.");
ff.error("This will stick", true);
Now it's just a pity multiple calls to flash doesn't just append to an array, i.e. replace this line with
if (typeof $scope.messages !== 'undefined') {
$scope.messages = $scope.messages.concat(messages);
} else {
$scope.messages = messages;
}

Resources