I have a $watch setup to monitor an array (menuItems) of values in a service (MenuFilter).
$scope.filterMenuItems = MenuFilter.menuItems;
$scope.$watch(function () {
return MenuFilter.menuItems;
}, function (newVal, oldVal) {
if ( newVal !== oldVal ) {
$scope.filterMenuItems = newVal;
}
});
At a certain moment a menu item is deleted and I expect the list to be updated by the watch but it isn't. It is only updated when I refresh the page.
Make sure to add true as the third parameter to make a deep watch or better use $watchCollection.
The issue is that $watch only uses reference comparison, i.e. it does not monitor items inside your array - only the array reference itself (oldArray === newArray).
Related
I have a controller, MapController and a factory, controlService. controlService has objects with boolean values that are modified by a different controller. Currently, whenever a control's boolean checked value is changed, MapController runs a function on the control using $watch as follows:
$scope.$watch((function() {
return controlService.getDrawControl().checked;
}), function(newVal, oldVal) {
if (typeof newVal !== 'undefined') {
vm.draw(newVal);
}
});
How do I achieve this same behavior following the recommendation in this answer to a similar question: Update scope value when service data is changed
I'm confused on how the value stays updated, and on top of that, how to run a function each time the view associated with the MapController is loaded based on that value.
Here's what I have:
$scope.$watch('someVar', function (val) {
$scope.someList.push(val);
execLongFunction.then(function (data) {
val.computedValue = data;
})
})
What I want: to add a value to the array in my scope, then perform some calculations and then (maybe) update the scope. The main point is that I want val to be pushed to scope array before execLongFunction is done.
The problem is: all $watch callback code is executed, kind of, immediately: even if I add some console.log() I see all of them at the same time.
What am I doing wrong?
$scope.$watch('someVar', function (newVal,oldVal) {
if(newVal != oldVal){
$scope.someList.push(newVal);
execLongFunction.then(function (data) {
val.computedValue = data;
})
}
});
$watch returns both oldValue and newValue of the $scope variable you are watching. Try to use those and execute your functions accordingly. This should work if the value of 'someVar' doesn't change every time when updated from the directive.
I have a web page that serves as the editor for a single entity, which sits as a deep graph in the $scope.fieldcontainer property. After I get a response from my REST API (via $resource), I add a watch to 'fieldcontainer'. I am using this watch to detect if the page/entity is "dirty". Right now I'm making the save button bounce but really I want to make the save button invisible until the user dirties the model.
What I am getting is a single trigger of the watch, which I think is happening because the .fieldcontainer = ... assignment takes place immediately after I create my watch. I was thinking of just using a "dirtyCount" property to absorb the initial false alarm but that feels very hacky ... and I figured there has to be an "Angular idiomatic" way to deal with this - I'm not the only one using a watch to detect a dirty model.
Here's the code where I set my watch:
$scope.fieldcontainer = Message.get({id: $scope.entityId },
function(message,headers) {
$scope.$watch('fieldcontainer',
function() {
console.log("model is dirty.");
if ($scope.visibility.saveButton) {
$('#saveMessageButtonRow').effect("bounce", { times:5, direction: 'right' }, 300);
}
}, true);
});
I just keep thinking there's got to be a cleaner way to do this than guarding my "UI dirtying" code with an "if (dirtyCount >0)"...
The first time the listener is called, the old value and the new value will be identical. So just do this:
$scope.$watch('fieldcontainer', function(newValue, oldValue) {
if (newValue !== oldValue) {
// do whatever you were going to do
}
});
This is actually the way the Angular docs recommend handling it:
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
set a flag just before the initial load,
var initializing = true
and then when the first $watch fires, do
$scope.$watch('fieldcontainer', function() {
if (initializing) {
$timeout(function() { initializing = false; });
} else {
// do whatever you were going to do
}
});
The flag will be tear down just at the end of the current digest cycle, so next change won't be blocked.
I realize this question has been answered, however I have a suggestion:
$scope.$watch('fieldcontainer', function (new_fieldcontainer, old_fieldcontainer) {
if (typeof old_fieldcontainer === 'undefined') return;
// Other code for handling changed object here.
});
Using flags works but has a bit of a code smell to it don't you think?
During initial loading of current values old value field is undefined. So the example below helps you for excluding initial loadings.
$scope.$watch('fieldcontainer',
function(newValue, oldValue) {
if (newValue && oldValue && newValue != oldValue) {
// here what to do
}
}), true;
Just valid the state of the new val:
$scope.$watch('fieldcontainer',function(newVal) {
if(angular.isDefined(newVal)){
//Do something
}
});
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?
I have a web page that serves as the editor for a single entity, which sits as a deep graph in the $scope.fieldcontainer property. After I get a response from my REST API (via $resource), I add a watch to 'fieldcontainer'. I am using this watch to detect if the page/entity is "dirty". Right now I'm making the save button bounce but really I want to make the save button invisible until the user dirties the model.
What I am getting is a single trigger of the watch, which I think is happening because the .fieldcontainer = ... assignment takes place immediately after I create my watch. I was thinking of just using a "dirtyCount" property to absorb the initial false alarm but that feels very hacky ... and I figured there has to be an "Angular idiomatic" way to deal with this - I'm not the only one using a watch to detect a dirty model.
Here's the code where I set my watch:
$scope.fieldcontainer = Message.get({id: $scope.entityId },
function(message,headers) {
$scope.$watch('fieldcontainer',
function() {
console.log("model is dirty.");
if ($scope.visibility.saveButton) {
$('#saveMessageButtonRow').effect("bounce", { times:5, direction: 'right' }, 300);
}
}, true);
});
I just keep thinking there's got to be a cleaner way to do this than guarding my "UI dirtying" code with an "if (dirtyCount >0)"...
The first time the listener is called, the old value and the new value will be identical. So just do this:
$scope.$watch('fieldcontainer', function(newValue, oldValue) {
if (newValue !== oldValue) {
// do whatever you were going to do
}
});
This is actually the way the Angular docs recommend handling it:
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
set a flag just before the initial load,
var initializing = true
and then when the first $watch fires, do
$scope.$watch('fieldcontainer', function() {
if (initializing) {
$timeout(function() { initializing = false; });
} else {
// do whatever you were going to do
}
});
The flag will be tear down just at the end of the current digest cycle, so next change won't be blocked.
I realize this question has been answered, however I have a suggestion:
$scope.$watch('fieldcontainer', function (new_fieldcontainer, old_fieldcontainer) {
if (typeof old_fieldcontainer === 'undefined') return;
// Other code for handling changed object here.
});
Using flags works but has a bit of a code smell to it don't you think?
During initial loading of current values old value field is undefined. So the example below helps you for excluding initial loadings.
$scope.$watch('fieldcontainer',
function(newValue, oldValue) {
if (newValue && oldValue && newValue != oldValue) {
// here what to do
}
}), true;
Just valid the state of the new val:
$scope.$watch('fieldcontainer',function(newVal) {
if(angular.isDefined(newVal)){
//Do something
}
});