I am building a user session activity timer using AngularJS 1.6. I am using $scope.$watch to monitor the value of a countdown timer. This works for the most part although $watch is binding to the variable multiple times, every time the session timer function is called. To solve this, I think I need to simply unbind the watcher if it is already defined.
I saw some very simple examples on how to do this on SO, however nothing seems to be working. The $scope.$watch method returns a function that when called, unbinds the watcher.
However, anytime the unbind function is called, there is an error in the console.log 'ReferenceError: sessionWatchUnbind is not defined'. This seems very strange because the undefined error is happening within an IF statement that checks if the function is defined before calling it. How could it be defined and then undefined immediately after?
// If session countdown watch is already set unbind it to prevent multiple watches on the same value
if (sessionWatchUnbind) {
console.log('sessionWatchUnbind ', sessionWatchUnbind); // This prints the function definition to the console log properly
sessionWatchUnbind(); // This line throws an error 'ReferenceError: sessionWatchUnbind is not defined' but it was defined in the previous lines!?
}
// Set a watcher on session countdown value
sessionWatchUnbind = $scope.$watch('session_countdown', function (newValue, oldValue) {
if (newValue !== oldValue) {
// continued...
}
});
Also, the same error happens if I try placing the call inside the body of the watcher as such.
// Set a watcher on session countdown value
sessionWatchUnbind = $scope.$watch('session_countdown', function (newValue, oldValue) {
if (newValue !== oldValue) {
sessionWatchUnbind(); // This line throws an error ReferenceError: sessionWatchUnbind is not defined
// continued...
}
});
I am pretty sure my code is following the numerous examples on how to do this. Perhaps someone could point me in the right direction!
UPDATE:
I got this to work by binding sessionWatchUnbind to 'this'. Perhaps because the outer wrapper function is bound to 'this' as well. This solution was based on the accepted answer here: How to prevent/unbind previous $watch in angularjs
vm = this;
vm.startActivity = function () {
// If session countdown watch is already set unbind it to prevent multiple watches on the same value
if (vm.sessionWatchUnbind) {
vm.sessionWatchUnbind();
}
// Set a watcher on session countdown value
vm.sessionWatchUnbind = $scope.$watch('session_countdown', function (newValue, oldValue) {
console.log('sessionWatchUnbind newValue, oldValue ', newValue, oldValue);
if (newValue !== oldValue) {
//continued...
}
});
This code works well for me:
const watcher = $scope.$watch('variable', value => {
// Remove watcher
if (value)
watcher();
});
Related
I have a service making two consecutive calls to an API asynchronously.
I would like the app to wait for both to be resolved before proceeding and since one of calls may or may not be made, I believe $watch is the way to go versus nested or chained callbacks.
var response_complete = {call1:false, call2:false};
$http.post("myapi.com/slug", data, header).then(function(res){
/* ... */
response_complete.call1 = true;
});
if(make_this_call==true){
$http.post("myapi.com/anotherslug", data, header).then(function(res){
/*...*/
response_complete.call2 = true;
});
} else response_complete.call2 = true;
$scope.$watch("response_complete",function(){
if(response_complete.call1==true && response_complete.call2==true){
console.log("DONE!");
}
});
So the idea is to create a global variable, and watch it as the two calls complete. The second call, which is conditional, immediately sets it's response variable to true if it is not being made.
But the $watch callback is only fired once and the condition within it (call1 & call2 == true) is never met.
your watch do not work as response complete is not a $scope variable | property:
// replace this with $scope property declaration
//var response_complete = {call1:false, call2:false};
$scope.response_complete = {call1:false, call2:false};
then in your succeeding code use $scope.response_complete to modify its value and so your $watch will be triggered as $scope.response_complete changed.
A better solution:
As others have specified it is better to use $broadcast than $watch, so instead watching the variable throw events instead and catch those event inside your $scope.
$http.post("myapi.com/slug", data, header).then(function() {
// stuff
$scope.$broadcast("POST_SLUG_COMPLETE");
});
$http.post("myapi.com/anotherslug", data, header).then(function() {
// stuff
$scope.$broadcast("POST_ANOTHERSLUG_COMPLETE");
});
// then in your $scope
$scope.$on("POST_SLUG_COMPLETE", function () {
// stuff
});
$scope.$on("POST_ANOTHERSLUG_COMPLETE", function () {
// stuff
});
hope that helps
If you need your "global" variable for the current scope, you can just do:
$scope.complete = false;
$http.post("myapi.com/slug", data, header).then(function(res) {
$http.post("myapi.com/anotherslug", data, header).then(function(res) {
$scope.complete = true;
console.log("DONE!");
});
});
You may also use $rootScope for a more "global" value. Other alternatives are $broadcast or a property inside a service.
But more important is to ensure how are you using the async calls. If you want both to be resolved put the second call inside the first. The sample provided by you wouldn't work because response_complete.call1 = true is inside an async thread and it is always false by the time you try to verify it
$rootScope.$on('$stateChangeStart', function (event, state) {
...
})
result -> es lint error The "$on" call should be assigned to a variable, in order to be destroyed during the $destroy event
but if i correct it like in the documentation
var unregister = $rootScope.$on('$stateChangeStart', function (event, state) {
...
})
i get "unregister is defined but never used" error
What is the best way to correct this issue?
The unregister variable that is returned by $rootScope.$on is a function that needs to be called to unregister the watch. A common use case is to call it when the current scope is destroyed:
$scope.$on('$destroy', unregister);
I currently am writing functional tests in Intern and have run across a small issue.
During the before portion of my test suite, I make an ajax call to an API to retrieve a variable's value. This variable being set is critical to the next step in the functional test, and therefore I want to halt the test until the variable is returned from the ajax call.
I read about Leadfoot's pollUntil() function and it sounded like it did what I needed to do. I wrote the following code:
var valueThatChanges = 0;
// ... (some functional test setup stuff)
//Ajax call that sets value of valueThatChanges
.then(function() {
return ajaxCall(valueThatChanges);
})
//valueThatChanges is initially 0 before/during ajax call
//Is set to a randomly generated value that is non-zero after response recieved
.then(pollUntil(function(valueThatChanges) {
return valueThatChanges !== 0 ? true : null;
},[valueThatChanges], 30000, 100))
.then(function() { //On success
console.log('Value is no longer zero.')
}, function(error) { //On failure/timeout
console.log(error)
})
});
However this does not work as the function enters the success callback instantly despite the value of valueThatChanges still being 0.
I understand that pollUntil() may not designed to handle situations like this (since I am not directly dealing with DOM elements in the pollUntil), but I am not sure why it does not work for this specific scenario.
It seems as though pollUntil() is not passing the updated variable on each call of it's polling function.
Can pollUntil() handle triggering an event on a change of variable value?
The general use case for pollUntil is a situation where you need to wait for something to happen in the remote browser. For example, pollUntil is often used to wait for a functional test page to fully initialize:
// ---------------------
// functional test (in Node.js)
this.remote.get('testpage.html')
.then(pollUntil('return window.pageIsReady ? true : null'))
// rest of test
// ---------------------
// remote test page (in the browser)
<script>
var pageIsReady = false;
require( ..., function ( ... ) {
// do setup stuff
pageIsReady = true;
});
</script>
If you're doing some bit of async stuff in your test setup that doesn't involve the browser, return a Promise from the before function in your test suite that will resolve when the async action is complete.
var valueThatChanges;
registerSuite({
before: function () {
return new Promise(function (resolve) {
// Assuming ajaxCall calls a callback when it's finished:
ajaxCall(function (newValue) {
valueThatChanges = newValue;
resolve();
});
});
},
test1: function () {
return this.remote
// rest of test
},
// ...
});
I have a code snippet:
var app = angular.module('Demo', []);
app.controller('DemoCtrl', function ($scope) {
function notify(newValue, oldValue) {
console.log('%s => %s', oldValue, newValue);
}
$scope.$watch('collection.length', notify);
$scope.$watch('my', notify);
$scope.collection = [];
$scope.my = 'hello';
});
$watch fires initially. And this code snippet will output:
0 => 0
hello => hello
Is it correct behavior? Of course I could check values for for equality, but what reasons for such as behaviour?
P.S. You could try this sample online: http://jsbin.com/otakaw/7/edit
According to documentation:
The listener is called only when the value from the current
watchExpression and the previous call to watchExpression are not
equal (with the exception of the initial run, see below).
After a watcher is registered with the scope, the listener fn is
called asynchronously (via $evalAsync) to initialize the watcher.
In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this
scenario within the listener fn, you can compare the newVal and
oldVal. If these two values are identical (===) then the listener was
called due to initialization.
I want to execute some code after AngularJS finished to change the HTML after an event. I've tried to do the following:
angular.module('ngC', [], function($routeProvider, $locationProvider)
{
$locationProvider.html5Mode(true);
})
.directive("carousel", function () {
return function (scope, element, attrs) {
scope.$watch("imgs", function (value)
{
// Here my code
});
};
});
The problem is that my code is execute before AngularJS replace {{}} code but I want execute it after.
Just compare if newValue is different from oldValue in the $watch listening function, this will indicate if something has changed or not.
scope.$watch("imgs", function (newValue, oldValue)
{
if(newValue !== oldValue) {
// Here my code
}
});
EDIT: From the docs
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.
I found my answer on: jQuery doesn't work in AngularJS ng-view properly
// (inside some function with $rootScope available)
$rootScope.$on('$viewContentLoaded', function() {
// ... (multiview init code here) ...
});