Refresh scope on every x time using $timeout - angularjs

I am a newbie to angular. I want to use $timeout of angular to refresh scope after few minutes. I am working on an social app where i need to refresh notification scope after few minutes. Getting notification from a http request using service.
JS:
App.factory('MyService' ,function($scope,$timeout){
return{
notification:return function(callback){
$timeout(function(){
$http.get("notification/get").success(callback)
},100000);
}
});
function Controller($scope,MyService){
MyService.notification(function(result){
$scope.notification =data;
});
}
Now how can i make http request after few minutes let'say 1 minute and refresh notification scope. I tried using $timeout but things are not working fine.

But i would suggest to move the $interval to the controller.
App.factory('MyService' ,function($scope,$timeout){
return{
notification: function(){
return $http.get("notification/get").success(function(response){
return response.data;
});
}
});
function Controller($scope,MyService,$interval){
/**
* Loads and populates the notifications
*/
this.loadNotifications = function (){
MyService.notification().then(function(data){
$scope.notification =data;
});
});
//Put in interval, first trigger after 10 seconds
var theInterval = $interval(function(){
this.loadNotifications();
}.bind(this), 10000);
$scope.$on('$destroy', function () {
$interval.cancel(theInterval)
});
//invoke initialy
this.loadNotifications();
}
This seems like a better architecture there.
Passing, resolving or rejecting promises will $digest the scope.
You want to get the notifications every x milliseconds and pass them into the scope.

You want to instantiate the timeout after it finishes. Try changing your $timeout function to something like:
var timer = $timeout( function refresh(){
$http.get("notification/get").success(callback)
timer = $timeout(refresh, 100000);
}, 100000);

Related

Cover code from promise with unit test

I have some logic inside my promise. Is it possible to cover this logic with unit tests? For example, I fire google analytics event inside this promise in my controller and want to make something like expect($analytics.eventTrack).toHaveBeenCalledWith(...).
Finally, the way to cover the code which is inside the promise can be the following:
let's suppose that on saving some data we fire a GA event inside our promise which says that the data was saved.
We have a service VoteService which has a method saveVote. This method makes a request to the server and returns a promise. Inside our promise, we fire GA event (when save was successfully executed).
To write the test for this we need in beforeEach:
create a promise saveVoteDeferrer = $q.defer();
resolve the promise with needed data
spyOn service's method
spyOn(VoteServiceMock,'saveVote')
.and.returnValue(saveVoteDeferrer.promise);
And then in it:
Call controller's method which contains service's method:
$ctrl.addVote();
verify if the GA event was fired inside our promise body
expect($analytics.eventTrack)
.toHaveBeenCalledWith('NewVoteAdded', {});
Bellow the sample:
voteController.js
var vm = this;
vm.addVote = function(){
//some logic goes here
VoteService.saveVote(vote)
.then(function(result){
//some logic goes here
$analytics.eventTrack('NewVoteAdded', {});
});
}
VoteService.js
return function(){
this.saveVote = function($http){
// some logic goes here
return $http.post(/*needed parameters*/);
}
}
vote.spec.js
describe('some text', function(){
beforeEach(function () {
// inject what you need
// mock VoteService service
// inject voteController controller
var saveVoteDeferrer = $q.defer();
spyOn($analytics,'eventTrack');
saveVoteDeferrer.resolve({ data: { } });
spyOn(VoteServiceMock,'saveVote')
.and.returnValue(saveVoteDeferrer.promise);
it('fire GA event when the vote is saved', function(){
$ctrl.addVote();
expect($analytics.eventTrack)
.toHaveBeenCalledWith('NewVoteAdded', {}); });
});
});

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.

Socket.io run function on update

How do I run a function when socket.io updates a variable. It seems that $watch does not work or purhaps I am doing something wrong.
$http.get('/api/availabilitys/' + myId).then(response => {
$scope.availability = response.data;
socket.syncUpdates('availability', $scope.availability);
});
$scope.$watch('availability', function() {
console.log('test'); //This is not printing on update
angular.forEach(self.location, function (loc){
loc.availability = $scope.availability.filter(function (a){
return a.loc === loc._id;
});
});
});
The functions have to be in the same controller / scope because of encapsulation. The $scope of angular does not know if socket.io updates a variable, use a socket.on() listener to trigger the angular $watch
Try the code in this thread Socket.IO message doesn't update Angular variable
Instead of $watch in angular you can use socket.on() of socket.io
var container = angular.module("AdminApp", []);
container.controller("StatsController", function($scope) {
var socket = io.connect();
socket.on('message', function (msg) {
console.log(msg);
$scope.$apply(function() { $scope.frontEnd = msg; });
});
});
See (the posts at the end of) this thread
How do I use $scope.$watch and $scope.$apply in AngularJS?

Error: $digest already in progress in angularjs when using alert

I am just getting the json data from the services in the controller.
And I am using a callback function to print the success message when it got loaded. It is working fine but it is also throwing an error which I mentioned in the question
//JSON file
{
"pc":"name"
}
// angular services
var service = angular.module('Services', ['ngResource']).
factory('Widgets', function($resource){
return $resource('/json/home.json', {}, {
query: {method:'GET', params:{}, isArray:false}
});
});
//controller
function editWidget($scope, Widgets) {
$scope.data = Widgets.query(function(data) {
alert("Success Data Loaded ---> " + JSON.stringify(data.pc));
});
}
alert, as well as confirm and prompt will pause the execution of code (blocks the thread), during which timeouts and intervals go all haywire if they should have been triggered during the pause. The $digest loop is made up of two smaller loops, which process $evalAsync queue and the $watch list. The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser's view render. This is usually done with setTimeout(0). Your alert during this time causes the problem.
You can use $timeout to execute an alert after the digest cycle is done and this way avoids this error.
$timeout(function () {
alert('Alert text');
});
Also don't forget to inject $timeout into your directive
if(!confirm('Your message')){
return false;
}else {
return false;
}
Return false in both cases.
#TheSharpieOne is right, It's work for me.
function delayalert(messagestr){
setTimeout(function(){
alert(messagestr);
},0);
}

Resources