Is there a way to add a timeout to an AngularJS $watch function?
For example, let's say I have the below AngularJS code that's watching a value, myName. When the value changes, the listener function runs. But if the value does not change within a certain period of time, I want it to do something else.
Specifically, in the code below, I would want $scope.nothingEnteredFlag to change from false to true. My html template be set up to reflect the state of that flag (e.g., using ng-show).
var app = angular.module("helloApp", []);
app.controller("helloCtrl", function($scope) {
$scope.nothingEnteredFlag=false;
$scope.$watch("myName", function (newValue, oldValue) {
if ($scope.myName.length < 5) {
$scope.message = "Short name!";
} else {
$scope.message = "Long name!";
}
});
});
See fiddle.
I've tried surrounding the $watch with $timeout, but can't seem to get that to work.
You can use angular timeout to achieve your desire result.
var timer;
var timerFunction = function() {
timer = $timeout(function() {
$scope.nothingEnteredFlag = true;
}, 5000);
};
This is will create the timer function
Your controller should like this
var app = angular.module("helloApp", []);
app.controller("helloCtrl", function($scope, $timeout) {
$scope.nothingEnteredFlag = false;
$scope.myName = "";
$scope.$watch("myName", function(newValue, oldValue) {
if ($scope.myName.length < 5) {
$scope.message = "Short name!";
} else {
$scope.message = "Long name!";
}
$scope.nothingEnteredFlag = false;
$timeout.cancel(timer);
timerFunction();
});
var timer;
var timerFunction = function() {
timer = $timeout(function() {
$scope.nothingEnteredFlag = true;
}, 5000);
};
timerFunction();
});
As you can see we have enabled timeout of the 5 seconds once user enters any text we cancel the timer and enable it again, this way we can prompt the user to enter if he hasn't wrote anything in five seconds.
Demo
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 very new to angularjs and I want to establish a connection to my server and dynamically show the result to user. so far I've tried:
angular.module('myApp.controllers', []).controller('socketsController', function($scope) {
$scope.socket = {
client: null,
stomp: null
};
$scope.reconnect = function() {
setTimeout($scope.initSockets, 10000);
};
$scope.notify = function(message) {
$scope.result = message.body;
};
$scope.initSockets = function() {
$scope.socket.client = new SockJS('/resources');
$scope.socket.stomp = Stomp.over($scope.socket.client);
$scope.socket.stomp.connect({}, function() {
$scope.socket.stomp.subscribe('/user/topic/messages', $scope.notify);
});
$scope.socket.client.onclose = $scope.reconnect;
};
$scope.initSockets();
});
But when I use {{result}} nothing appears.
UPDATE
The server response is totally right with console.log(message.body).
I guess, the callback is not taking the scope properly. Try call $scope.$apply(); after you attach the message.body to result :
$scope.notify = function(message) {
$scope.result = message.body;
$scope.$apply();
};
$scope.$apply() triggers an angular digest cycle whcih will update all the bindings..
Call it inside a timeout function but inject $timeout first it will call the digest cycle and update the value.
$timeout(function(){
$scope.result = message.body;});
For example, let's assume I need to run a function after receiving two events "eventA" and "eventB". What I usually do is to declare for each event a boolean variable, set the variable to true when the event is received, and ask if the other variable is true to run the function:
var a = false,
b = false;
$scope.$on("eventA", function(){
a = true;
if (b)
performTask();
});
$scope.$on("eventB", function(){
b = true;
if (a)
performTask();
});
var performTask = function() {
/* do something... */
};
This gets more complex if there are three or more events. Is there a design pattern to handle these cases?
You can use $q promises.
var dfdATask= $q.defer();
var dfdBTask= $q.defer();
$scope.$on("eventA", function(){
// whatever this function does
dfdATask.resolve(true);//or pass a value
});
$scope.$on("eventB", function(){
//whatever this function does
dfdBTask.resolve(true);//or pass a value
});
$q.all([dfdATask.promise, dfdBTask.promise]).then(function(){
//be sure to pass in an array of promises
//perform task
})
So theory wise if you only want to execute this magical action after you've received these two events have been called at least once then you probably want to use promises.
app.controller('ExampleOneController', [
'$log',
'$scope',
'$q',
'$rootScope',
function ($log, $scope, $q, $rootScope) {
$scope.anotherAction1FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-1-a', function () {
$log.log('Fired e-1-a');
aDeferred.resolve();
});
$scope.$on('e-1-b', function () {
$log.log('Fired e-1-b');
bDeferred.resolve();
});
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 1!');
$scope.anotherAction1 = 'Hello World 1!';
$scope.anotherAction1FiredCount++;
});
}
]);
That being said usually I want to execute everytime two things happen so I tend to 'reset' my promises.
app.controller('ExampleTwoController', [
'$log',
'$scope',
'$q',
function ($log, $scope, $q) {
$scope.anotherAction2FiredCount = 0;
var aDeferred = $q.defer(),
bDeferred = $q.defer();
$scope.$on('e-2-a', function () {
$log.log('Fired e-2-a');
aDeferred.resolve();
});
$scope.$on('e-2-b', function () {
$log.log('Fired e-2-b');
bDeferred.resolve();
});
var wait = function () {
$q.all([aDeferred.promise, bDeferred.promise]).then(function () {
$log.log('Fired another action 2!');
$scope.anotherAction2 = 'Hello World 2!';
$scope.anotherAction2FiredCount++;
aDeferred = $q.defer();
bDeferred = $q.defer();
wait();
});
};
wait();
}
]);
Here's the working plunker!
Promises are life.
active polling using $scope.$watch:
One way to do this:
var a = false, b = false;
$scope.$on("eventA", function(){ a = true; });
$scope.$on("eventB", function(){ b = true; });
$scope.$watch(
function() { return a && b; },
function(newval, oldval) {
if (newval) { performTask(); }
}
);
one step further:
var events = { a: false, b: false };
$scope.$on("eventA", function(){ events.a = true; });
$scope.$on("eventB", function(){ events.b = true; });
$scope.$watch(
function() {
var result = true;
for (var key in events) {
result = result && events[key];
}
return result;
},
function(newval, oldval) {
if (newval) { performTask(); }
}
);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
Be sure to read the developer guide and check the "Scope $watch Performance Considerations" section.
regular callback:
var events = { a: false, b: false };
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
performTask();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with one promise, $q.defer():
var events = { a: false, b: false };
var shouldPerform = $q.defer();
function checkIfPerfomTask() {
for (var key in events) {
if (!events[key]) { return; }
}
shouldPerform.resolve();
}
$scope.$on("eventA", function(){ events.a = true; checkIfPerfomTask(); });
$scope.$on("eventB", function(){ events.b = true; checkIfPerfomTask(); });
shouldPerform.promise.then(performTask);
http://plnkr.co/edit/5NrOhTwblMCCCoKncVAW?p=preview
with multiple promises...
Already been covered by multiple answers.
Promises are meant for your use case. But since you mentioned you were looking for a design pattern, I'll put down one way to do this using the Observer pattern.
You can check out this live Plunkr: http://plnkr.co/edit/1Oqn2TAGTr7NLYZd9ax1?p=preview
Has an angularjs service that handles the logic for tracking events and calling the final action.
The controller simply defines your events, the final event and registers them with your service.
app.controller('MainCtrl', function($scope, EventService) {
var events = [];
... //define events
EventService.registerEvents(events);
EventService.registerEventsCallback(finalEvent); //the observer
});
The service makes this work by removing a called event from the events list upon first execution.
app.factory('EventService', function(){
var events = [];
var finalEvent;
var eventsCallback = function(){
if(!events.length){
finalEvent();
}
}
var resolveEvent= function(event){
var eventIndex = events.indexOf(event);
if(eventIndex>=0){
events.splice(eventIndex,1);
}
}
return{
registerEvents: function(eventsList){
events = angular.copy(eventsList);
},
registerEventsCallback: function(event){
finalEvent = event;
},
publishEvent: function(event){
event();
resolveEvent(event);
eventsCallback();
}
}
});
$watch() is not catching return sseHandler.result.cpuResult.timestamp after the first iteration. I'm not sure why, because I verified the datestamps are changing. Also, after the first iteration....if I click on the view repeatedly, the scope variables and view update with the new information...so it's like $watch does work...but only if I click on the view manually to make it work.
'use strict';
angular.module('monitorApp')
.controller('homeCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.controller('cpuCtrl', ['$scope', 'sseHandler', function($scope, sseHandler) {
$scope.sseHandler = sseHandler;
$scope.avaiable = "";
$scope.apiTimeStamp = sseHandler.result.cpuResult.timestamp;
$scope.infoReceived = "";
$scope.last15 = "";
$scope.last5 = "";
$scope.lastMinute = "";
var cpuUpdate = function (result) {
$scope.available = result.cpuResult.avaiable;
$scope.apiTimeStamp = result.cpuResult.timestamp;
$scope.infoReceived = new Date();
$scope.last15 = result.cpuResult.metrics['15m'].data
$scope.last5 = result.cpuResult.metrics['5m'].data
$scope.lastMinute = result.cpuResult.metrics['1m'].data
}
$scope.$watch(function () {
console.log("being caught");
return sseHandler.result.cpuResult.timestamp},
function(){
console.log("sss");
cpuUpdate(sseHandler.result);
});
}]);
angular.module('monitorApp')
.controller('filesystemsCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.controller('httpPortCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.factory('sseHandler', function ($timeout) {
var source = new EventSource('/subscribe');
var sseHandler = {};
sseHandler.result = { "cpuResult" : { timestamp : '1'} };
source.addEventListener('message', function(e) {
result = JSON.parse(e.data);
event = Object.keys(result)[0];
switch(event) {
case "cpuResult":
sseHandler.result = result;
console.log(sseHandler.result.cpuResult.timestamp);
break;
}
});
return sseHandler;
});
The changes in sseHandler.result.cpuResult.timestamp happen otuside of the Angular context (in the asynchronously executed event-listener callback), so Angular does not know about the changes.
You need to manually trigger a $digest loop, by calling $rootScope.$apply():
.factory('sseHandler', function ($rootScope, $timeout) {
...
source.addEventListener('message', function (e) {
$rootScope.$apply(function () {
// Put all code in here, so Angular can also handle exceptions
// as if they happened inside the Angular context.
...
});
}
...
The reason your random clicking around the app made it work, is because you probably triggered some other action (e.g. changed a model, triggered and ngClick event etc) which in turn triggered a $digest cycle.
Your message event in the EventListener does not start a new digest cycle. In your sseHandler try:
$timeout(function () {sseHandler.result = result;});
I'm trying to make a little notifier, that informs about typical situations: need authorization, changes saved etc. Notices are shown for 3 seconds and disappear, if user didn't click on it (if notice clicked, it disappears immediatly).
Documentation is not very informative.
How should i use $timeout, to call close(); after 3 seconds?
And how can i put a variable (nId) into function? I tried with closure (*function(){return function(){}}*) in default setTimeOut(), but unsuccessfully.
myApp.controller('noticesCtrl',
function noticesCtrl($scope, $rootScope, noticesData){
$rootScope.notices = [];
$scope.closeNotice = function(nId){
noticesData.close(nId);
};
});
myApp.factory('noticesData', function($rootScope, $timeout){
return{
add: function(type, text){
var nId = $rootScope.notices.length + 1;
$rootScope.notices.push({id: nId, type:type, text:text+nId});
// call close function with 3sec. delay; how?
},
close: function(nId){
angular.forEach($rootScope.notices, function(notice, key){
if(notice.id == nId){
$rootScope.notices.splice(key,1);
}
});
}
}
});
myApp.factory('noticesData', function($rootScope, $timeout){
var obj = {};
obj.add = function(type, text){
var nId = $rootScope.notices.length + 1;
$rootScope.notices.push({id: nId, type:type, text:text+nId});
$timeout(function(){
obj.close(nId);
},3000);
}
obj.close = function(nId){
angular.forEach($rootScope.notices, function(notice, key){
if(notice.id == nId){
$rootScope.notices.splice(key,1);
}
});
}
return obj;
});
Heres how to do it
$timeout(function () {
// do something
}, 50);