wondering if there is a way in angular to watch a function like you would a variable
function foo() {
console.log("foo called");
}
$scope.$watch('foo', function {
console.log("foo watched");
});
So given the syntax is correct, will "foo watched" be printed to the console, if not, is there a way to have that watch function run?
EDIT
Reformatting question to more of what I need:
Let's say I have a button and an text input, each handled by different controllers, say btnCtrl and txtCtrl
There is also a boolean global flag stored as $scope.flag which changes the validation standards for the input.
//btnCtrl
//called on button click
function onButtonClick() {
$scope.flag = !$scope.flag
}
//txtCtrl
//called on input change
function onInputChange() {
handleValidations()
}
function handleValidations() {
//set error indicator on invalid input
//valid input changes based on $scope.flag value
if($scope.flag) {
//check validation1
} else {
//check validation2
}
}
I want to check the input box validations, handleValidations when $scope.flag is changed. $scope.flag is changed frequently not by the button, but by other functions.
So, I would like to call handleValidations not when $scope.flag is changed, but when onButtonClick is called. What is the best way to do that?
My initial thought was to put some sort of $scope.$watch on onButtonClick.
You can watch the functions. Have you tried running your code? If you call foo, it should print both console statements. There is a 'gotcha' for watching functions though, mastering $watch in Angularjs
One key aspect to note here, is that if the expression is evaluated as a function, then that function needs to be idempotent. In other words, for the same set of inputs it should always return the same output. If this is not the case, Angular will assume that the data being watched has changed. In turn, this means that it will keep detecting a difference and call the listener at every iteration of the digest cycle.
With the help of the comments and other answers:
I sent a local message using $rootscope from btnCtrl and added a listener to txtCtrl that calls handleValidations
Thank you all for the help
Related
I am using angular-meteor and would like to perform a function on each object. I tried running this function within an ng-repeat in the view, but I am getting massive amounts of function calls and can't figure out why. I tried to make it as simple as possible to demonstrate what is going on.
constructor($scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.loaderCount = 0;
this.helpers({
loaders() {
return Loaders.find( {isloader:true}, {sort: { name : 1 } })
}
});
That gives me 26 Loaders. My function just adds 1 to the count every time the function is called:
displayLoaderCount()
{
return ++this.loaderCount;
}
Now in my view, I am looping through each loader, and calling the function. This should in my mind give me 26, but instead I am getting 3836.
<tr ng-repeat="loader in loaderExhaustion.loaders">
<td>{{loaderExhaustion.displayLoaderCount()}}</td>
Can anyone help explain this to me? Ideally I would like to loop over the contents in my module but as the collection is async, when the loop starts the length of the collection is 0, hence why I made the call in the view.
THANKS!
Every time angular enters a change detection cycle, it evaluates loaderExhaustion.displayLoaderCount(), to know if the result of this expression has changed, and update the DOM if it has. This function changes the state of the controller (since it increments this.loaderCount), which thus triggers an additional change detection loop, which reevaluates the expression, which changes the state of the controller, etc. etc.
You MAY NOT change the state in an expression like that. For a given state, angular should be able to call this function twice, and get the same result twice. Expressions like these must NOT have side effects.
I can't understand what you want to achieve by doing so, so it's hard to tell what you should do instead.
This one has me confounded. I have looked far and wide and am out of ideas. In my searching, I discovered that one of the common reasons for multiple function calls on load is if you have a controller defined in routes and via the ngController directive. Checked this - I do not use ngController. I also checked my routes - seem in order. There are no $watch functions that could be causing $digest issues. This function is called one time, at the bottom of the function, and the console.log is logged out 5x...EVERY TIME. I have even set a $timer function and it still calls it 5x. Have tried creating a variable to only run if it hasn't been run before but it seems like it's all happening with the getQuotas() method. Any help would be greatly appreciated!
function getQuotas ()
{
console.log('getQuotas'); //This logs out 5x
UserService.getQuotas()
.then(function(res)
{
if (res.data.success)
{
quotaData = res.data.data;
getQuotas_success();
return true;
}
else
{
getQuotas_failure();
return false;
}
}, function (err)
{
getQuotas_failure();
return false;
});
}
getQuotas(); //Function is called here.
Solved it! I'm hopeful this will help others. There was a custom attribute directive on each of 4 input fields on this page. That particular directive was using the same controller as the page itself. So the controller was getting loaded a total of 5 times. Fortunately for me, this directive is now deprecated but I would probably redo it by either creating a directive-level controller and using the 'require' attribute in the directive's return object, pointing to the page-level controller, OR just have the data that needs to get passed between the page-level controller and the directive go through a service.
I have a function inside a directive (in link attribite):
scope.$watch('pickerSettings', function(pickerSettings){
scope.pickerSettings = pickerSettings;
});
It watches for changes for pickerSettings in another directive to change the current value. Currect directive is used to call a modal. However - the directive only checks for the changes before the modal is opened. If it was opened - the settings are being saved and closing modal and changing the values in other directive will not change the settings. (By the way - modal opening is handled in another service).
I have added the code to log the object properties when opening the modal
scope.showGallery = function () {
console.log(scope);
MediaService.showImagePicker(scope);
};
It only logs the data when called for the first time.
I'm not quite sure why you're doing this:
scope.$watch('pickerSettings', function(pickerSettings){
scope.pickerSettings = pickerSettings;
});
This is watching the scope variable pickerSettings and on the initial digest and/or whenever angular detects a change it is re-assigning the detected change back to the same scope variable. If the scope variable itself is never reassigned to a different value, this watch will never fire again (and thus fire only once).
I'm guessing you probably want to react on a change on one of the sub properties. This can be done by specifying it in the watch expression like this:
scope.$watch('pickerSettings.mySubProperty', function(newValue){
/* handle magic here */
});
... or by using $watchGroup to watch an array of expressions:
scope.$watchGroup([
'pickerSettings.prop1',
'pickerSettings.prop2'
], function(newValues){
var prop1 = newValues[0], prop2 = newValues[1];
/* handle magic here */
});
... or by using $watchCollection to watch (one level deep) sub-properties:
scope.$watchCollection('pickerSettings', function(newValue){
/* handle magic here */
});
... or by using a custom watch function that is called every digest cycle:
function myCustomWatch() {
// If the return value of this function differs from
// the last digest cycle the watch callback is triggered.
if(scope.pickerSettings.items.length) {
return scope.pickerSettings.items[0].someValue;
}
return -1;
}
scope.$watch(myCustomWatch, function(newValue){
/* handle magic here */
});
Hope this helps.
I'm almost finished with a quiz of mine. I've encountered an error which I have finally pinpointed. But first I'll tell you some background info on the code.
Background
On stage, there exist 4 button components registered as movie clips. These are later stored in an array. When a certain icon is clicked on stage, these buttons are to be activated by adding event listeners to all of them. When a button is pressed, it checks if it's the 'correct' answer and removes the event listeners.
Now I have done extensive checks and narrowed down the problem to the following.
Code
This function will add button listeners to each button recursively. Note that the variable 'num' is a fixed integer between 1 - 4 which is generated earlier into the code and used for many 'if' cases.
function addButtonListener(num:int):void
{
for (var obj:Object in _buttons)
{
_buttons[obj].addEventListener(MouseEvent.CLICK, checkans(num));
}
}
This function is basically the opposite and also disables the buttons
function removeButtonListener(num:int):void
{
for (var obj:Object in _buttons)
{
_buttons[obj].removeEventListener(MouseEvent.CLICK, checkans(num));
_buttons[obj].enabled = false;
}
}
Now one thing I noticed through the use of trace functions is that the code correctly adds the button listeners but it does not remove them.
This function calls for each button to be removed.
function checkans(num:int):Function
{
return function(e:MouseEvent):void
{
if (e.currentTarget.label == xmlNodes)
{
points = points + (2 * num);
scoreBox.text = points.toString();
}
else
{
trace("Incorrect!");
}
if(myText.parent){
myText.parent.removeChild(myText)
}
closeShowQuestion(num);//closes a previous function
removeButtonListener(num);//Should I pass this variable into the end function?
GoFishing.addEventListener(MouseEvent.CLICK, fish);//Starts the process again.
}
}
So am I removing the event listeners incorrectly?
Do you need to see more code to be sure?
Thanks in advance!
removeEventListener must use the SAME function that was used in addEventListener.
Your checkans function always returns a NEW function, so it will not be the same for add and remove.
If you have to pass a num into your listener function, do it other way. Make it a property of a button or store externally etc.
AngularJS allows listening for changes of an object and will call the supplied callback function supplied to the $watch function. With a largish library using AngularJS like ngGrid objects are frequently changed that's being "watched".
Once the watch callback is invoked, how can one trace back the call site that caused the change to the object?
Without knowing what caused the change, and so caused the watch handler to be invoked, it is proving very difficult to debug a library like ngGrid. I'm setting breakpoints everywhere I can foresee the variable could be changed, and then trying to build a graph for the execution pipeline to follow the chain of events that lead to an object being changed.
You simply can't do that. $watch will just add a callback to check whether the object changed, to be run during digests.
I guess that's one of the main differences with frameworks like Backbone where you extend a Model object.
That being said, you might have better luck trying to $scope.$digest(); intentionally (updating the model, and firing the watchers), but it's a stretch...
Update
The problem is you're thinking there's a correlation between watches and model changes, but there simply isn't. Adding a watch just adds something to be checked when the digest loop runs.
This loop isn't triggered by changes to something on a $scope, but rather calls to $scope.$apply, or directly calling $scope.$digest.
Note that most (all?) of Angular's directives and related components call $scope.$apply on your behalf. For example, the reason why $timeout and ngClick work as expected, is because they run $scope.$apply internally after executing your callbacks.
Update II
If you're merely interested in finding the call site, could something like this help you?
$scope.foo = {
get bar () { return getting(); },
set bar (value) { setting(value); }
};
var bar;
function setting (value) {
var stack = getStack();
console.log(value, stack);
bar = value;
}
function getting () {
console.log(getStack());
}
function getStack () {
try {
throw new Error('foo');
} catch (e) {
return e.stack || e;
}
}
Well, you may have found the answer by now, but because I was looking for it, too, and could not find it so I am posting the answer here.
Let's assume your javascript variable is name, and you would like to find who changed that.
The method I have found is this:
Change name to name_
Add getter: get name() { return this.name_ ; }
Add setter: set name(value) { this.name_ = value ; }
Put breakpoint in the setter, and watch the call stack when breakpoint is triggered.
Example for a structure:
Before:
scope = {
name:'',
};
After:
scope = {
name_:'',
get name() { return this.name_; },
set name(value) {
this.name_ = value;
alert('Changed!');
},
};
I hope this will help you and others!