Test if function has been called inside $scope.$watch - angularjs

I'm trying to figure out how to test with karma jasmine if function was executed inside $watch with the conditions I need.
Here what is in the controller. $watch contains couple of condition.
$scope.$watch('player', function (newVal, oldVal) {
if (oldVal && newVal != undefined && newVal != oldVal) {
if (newVal.email == oldVal.email && newVal.emWallet == oldVal.emWallet)
$scope.saveSettings();
}
}, true)
This is the part of the test
it('when player property is changed saveSettings calls', function () {
var sspy = spyOn(scope, "saveSettings");
expect(scope.player.email).toEqual('');
expect(scope.player.emWallet).toEqual('');
expect(scope.player.balance).toEqual(10.0000);
//change player balance
scope.player.balance = 10.0304;
scope.$apply();
expect(scope.player.email).toEqual('');
expect(scope.player.emWallet).toEqual('');
expect(sspy).toHaveBeenCalled();
});
In the test above I'm changing the property that is outside condition so player.email and player.emWallet still the same and expect the call of saveSettings() function inside, but get "sspy has never been called" error.
I would appreciate a lot if someone point me right direction.
Update:
When I change actual value scope.player.balance = 10.0304; in my code, the function fires, but test does not pass successfully

Related

Angular $watch - execute watch block only once

I have a simple $watch on a dropdown value :
$scope.view;
$scope.$watch('view', function(){
console.log(1);
// watch block
}
The value is chaining with ng-model:
<select ng-model="view" ng-options="x for x in name">
</select>
Due to the $digest the value 1 is printed twice, is there a way to tell angular to execute the the $watch block only once?
Simple answer: unregister the watch after the first successful run:
var viewWatch = $scope.$watch('view', function () {
viewWatch(); // Stops the watch
console.log(1);
//watch block
};
However this may or may not yield the results you want, because sometime the $watch is called upon initialization. You may want to set some conditions:
var viewWatch = $scope.$watch('view', function (currentValue, oldValue) {
if (currentValue && !oldValue) {
viewWatch(); // Stops the watch
console.log(1);
//watch block
}
};
You could simply test if view is about to be set for the first time (declared) or is about to be changed (by the select) by testing the params :
$scope.$watch('view'), function(newVal, oldVal) {
if (oldVal && oldVal != newVal) {
console.log(1)
}
}

Angular: Why is $watch being called?

Any reason why a $scope.$watch would be called when the value it is watching for does not exist anywhere within the app?
As you can see below, no matter what string I assign for the watchExpression , the function runs ...
$scope.$watch( 'kwhefjkewgfweahhfwujad' , // a bunch of random letters
function()
{ console.log("it fired ... why?");
}
) ;
Firefox does not indicate any errors.
try
$scope.$watch( 'kwhefjkewgfweahhfwujad', function(val) {
if (val) {
console.log("it fired ... why?");
}
});
watchers are running with every digest cycle. as you are watching primitive value, attached to the scope, you need to verify that a new value exists (and has changed).
you could also do
$scope.$watch( 'kwhefjkewgfweahhfwujad', function(newVal, oldVal) {
if (newVal !== oldVal) {
console.log("it fired ... why?", newVal, oldVal);
}
});

Re-registering $watch after de-registering

I have ng-model on a date variable. On changing that variable, I want to make some validations and during those validations I might change the start variable itself, and to make sure I don't get infinite-watch trigger, I'm de-registering the watch before, and re-registering it when the validation finishes.
The re-registering doesn't work. What am I doing wrong?
var watchStartTime = $scope.$watch('timeSelection.startTime', function(newValue, oldValue) {
if (newValue === oldValue) return;
validateStartEndTime();
}, true);
function validateStartEndTime() {
// De-register watch
watchStartTime();
// Do some stuff that might change timeSelection.startTime
// Re-register watch
watchStartTime();
}
I also tried setTimeout with 0 on the re-registering, thought it might work, but it doesn't.
the timeSelection.startTime changes (I know for a fact it does) but the function in $watch doesn't get called again.
What am I missing?
As stated by #Nikos Paraskevopoulos $watch returns the deregistration fn.
I think you should do something like the following...
var register = function() {
var unregister = $scope.$watch('timeSelection.startTime', function(newVal, oldVal) {
if (newVal === oldVal) {
return;
}
unregister();
validateStartEndTime();
register();
}, true);
};
register();

Angularjs adding $scope.$watch after $routeProvider's resolve

I'm having an issue where I am trying to watch a certain element from my scope after the resolve is done. For some reason, it gets run when i run the second line from this snippet and I dont seem to be able to add this $watch during the "resolve".
I understand that promises are asynch, but how can I know when my resolve is done to then add the watch?
The variableToWatch can be changed either by code or in the UI (otherwise i would've just used ng-change="doWork()")
$scope.variableToWatch = $route.current.locals.data.initialValue;
$scope.listOfDependantData = $route.current.locals.data.items;
$scope.$watch('variableToWatch', function (newValue) {
myService.getNewDependantData(newValue).$promise.then(
function (items) {
$scope.listOfDependantData = items;
}
);
};
Update:
If you want to run your code only when it changes after the initial setting of value, you can use the second form of the watchFunc passed as the second argument to $scope.$watch:
$scope.$watch('variableToWatch', function (newValue, oldValue) {
if (typeof newValue !== 'undefined' && newValue !== oldValue) {
// Use the new value here ...
}
});
I don't fully follow the question, but I suspect that you want to watch the value $route.current.locals.data.initialValue instead of $scope.varibleToWatch?
In that case, you can use the alternate form of $scope.$watch with a function as the first argument:
$scope.$watch(function () { return $route.current.local.data.intialValue; }, function (newValue) {
// ...
};
Or, if you want to watch the variable on your $scope which is referenced to by the sting contained in $route.current.local.data.initialValue, then you can use this:
$scope.$watch(function () { return $scope[$route.current.local.data.intialValue]; }, function (newValue) {
// ...
};
Does this solve your problem?

Angularjs: restoring model value in $watch after failed update on server side

Here's the scenario:
fiddle:
$scope.$watch('obj.value',function(val, oldVal) {
if (val === oldVal) return;
MyService.update($scope.obj, $scope.result).then(function(response){
$scope.results.push(response);
}, function(response) {
$scope.obj.value = oldVal;
$scope.results.push(response);
});
});
I set a watch on a value and update it to db whenever it changes. But if the update fails for some reason (connection problem, server error, invalid session, insufficient permissions, .etc), I'd like to restore that value to the previous version. In the fiddle you can see what happens if you select "reject deferred" and try to change the value - it starts infinte loop of failed requests, restored values and $watch triggers.
For the time being I'm setting a flag on the scope to indicate that request has failed and next $watch should not call the service. But I'm looking for ways to reduce this boilerplate code.
Of course, I could always use some other ways to inform the scope, for example ng-change, but then I lose reference to the old value. I could keep the reference in my scope, but that is even worse than current situation.
Have you any ideas how these situations should be handled? Basically what I'm looking for is a way to update the model in the $watch without triggering further $watches, if that's even possible.
Use ng-change directive instead of the watcher, and use an internal state variable to store the value oft the last successful save.
See it in action: http://jsfiddle.net/Zmetser/vscGP/6/
function MyCtrl($scope, MyService) {
var lastSaved;
$scope.obj = {value: "foo"};
$scope.results = [];
$scope.result = "1";
lastSaved = $scope.obj.value;
$scope.sentinel = function ( value ) {
MyService.update($scope.obj, $scope.result).then(function(response){
lastSaved = angular.copy($scope.obj.value);
$scope.results.push(response);
}, function(response) {
if ( lastSaved )
$scope.obj.value = lastSaved;
$scope.results.push(response);
});
};
}
<input type="text" ng-model="obj.value" ng-change="sentinel(obj.value)"/>
Rather than using a timeout or $timeout, you could also use a closure to encapsulate a boolean flag that you reset.
(function WatchWithRevert () {
var justReverted = false;
$scope.$watch('obj.value',function(val, oldVal) {
//if (val === oldVal) return;
if (justReverted) {
justReverted = false;
return;
}
MyService.update($scope.obj, $scope.result).then(function(response){
$scope.results.push(response);
}, function(response) {
$scope.obj.value = oldVal;
justReverted = true;
$scope.results.push(response);
});
});
})();
Basically, you are doing it right. The watcher watches the watched object, so the best thing you can do is to tell it to close one eye for next watch.
You can use $timeout to set a temporary flag that gets cleaned up ASAP
_skipWatch = false
rollBackLocally = (newVal, oldVal) ->
_skipWatch = true
angular.copy oldVal, newVal
# schedule flag reset just at the end of the current $digest cycle
$timeout (-> _skipWatch = false), 0
$scope.$watch 'obj.value', (newVal, oldVal) ->
return if _skipWatch
MyService.doSomething().then
((response) -> $scope.results.push(response)),
(-> rollBackLocally newVal, oldVal)

Resources