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.
Related
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();
});
On a function call I need to check for a change in a array element.
If any change occurs I need the function to continue or else to stop the execution.
$scope.myFunction = function() {
$scope.watchCollection('checkedCountry',function(newval,oldval) {
if (newval!==oldval) {
console.log("dothings"); // this is executing multiple times.
}
})
}
But for a single call of myFunction am getting output of multiple times dothings.
I need it to work only once for a function call and if the collection changes.
Based on the code you have shared, the number of watches that would be setup is directly proportional to the number of times the function myFunction is called. Therefore, everytime you call myFunction a new watch is setup. An change to the collection hence will trigger all the watches.
Normally watches are setup in some type of initialization function that is called only once.
I would suggest move your watch setup outside the myFunction call.
I could not find any other question/answer that met my needs, so here it is:
In an AngularJS (1.2.14) controller, I have an event listener that executes an ajax call to fetch some data when an event ('fetchData') is heard. After the fetch is successful, an event (called 'fetchSuccess') is broadcasted, that a directive is listening for (though that part is irrelevant).
$scope.$on('fetchData', function () {
$scope
.submitSearch()
.then(function (results) {
$scope.$broadcast('fetchSuccess');
}, function () {
$scope.$broadcast('fetchError');
});
});
So, in my test I want to do something like this (assume that 'this.scope' is a new $scope object on the controller in the test suite):
it('should broadcast "fetchSuccess"', inject(function ($rootScope) {
var scope = this.scope,
spy = chai.spy(scope, '$broadcast');
// trigger the $broadcast event that calls the fetch method
scope.$broadcast('fetchData');
$rootScope.$apply();
expect(scope.$broadcast).to.be.called.with('fetchSuccess');
}));
But I am not clear on how to listen for the $broadcast event in an assertion. I keep getting this error: AssertionError: expected function (name, args) {...}
Just to be clear, my issue is not with the functionality of the event broadcaster or the listeners during runtime; the application works as expected. The problem is with listening to the events in the test suite.
Note that the above code is just the necessary snippet that is needed for this question. In my application, there are other variables/methods that get set/called and those things test out correctly. Meaning that if I test to see if the actual fetching method gets called, or if a particular variable is being set appropriately, those tests pass.
I have tried mixing and matching the scope variables and even listening for the $broadcast via scope.$on('fetchSuccess', fn) but nothing seems to work. The fetchSuccess event doesn't seem to get emitted, or I'm not listening for it properly.
Thanks in advance,
Ryan.
So, I have found the answer to my question and it was all my fault. Though I did have to modify the way the test was written, the core problem as simple as listening for the wrong event!
But, for those that want to know what my test looked like, here is the final test (rootscope is being set to $rootScope elsewhere):
it('should broadcast a "fetchSuccess" event', function (done) {
var eventEmitted = false;
this.scope.$on('fetchSuccess', function () {
eventEmitted = true;
done();
});
this.scope.$broadcast('fetchData');
rootscope.$apply();
eventEmitted.should.be.true;
});
There was no need to spy on the $broadcast event, the AngularJS $on listener is sufficient.
Reading this excellent book, Mastering Web Development in AngularJS, I ran across this code:
var Restaurant = function ($q, $rootScope) {
var currentOrder;
this.takeOrder = function (orderedItems) {
currentOrder = {
deferred:$q.defer(),
items:orderedItems
};
return currentOrder.deferred.promise;
};
this.deliverOrder = function() {
currentOrder.deferred.resolve(currentOrder.items);
$rootScope.$digest();
};
this.problemWithOrder = function(reason) {
currentOrder.deferred.reject(reason);
$rootScope.$digest();
};
My understanding is that the $rootScope.$digest(); calls are made in order to alert Angular that the Promise's state has been updated.
Is my understanding correct? Also, is it necessary to make the above $rootScope.$digest(); calls?
$scope.$digest() is what processes all of the $watch events that are on the current and children scope. It essentially manually tells the scope to check if a scope variable has changed. You don't generally want to use this when you are inside of a controller or a directive, because the $scope.$apply() function calls the $digest anyway and it is called when you mutate a scope variable.
Checkout this link for an example.
You don't need a $rootScope.$digest here because resolving/rejecting the promise will fire a $rootScope.$digest internally, as $interval,$timeout,and $http (after request finished) do that for you. And this $digest can throw errors of $digest already in progress.
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) ...
});