Can anyone explain what happens behind the scenes when you bind to a function in AngularJS? What kind of watch does it create? I have a feeling it would create two watches (in my example below) one for each property that makes up the return value. However I'm certainly not sure about this but it feels like something we shouldn't do.
e.g.
<div ng-show="vm.someFunc()">
JS
vm.someFunc = function() {
return vm.property1 || vm.property2;
}
If you created the angular scope method "vm.someFunc()", this will continuously get polled. You can verify this by setting a breakpoint in this method, it will continuously keep getting hit. If you check the task manager and show the browser running your website, the memory keeps going up and won't stop.
In my opinion, scope functions should only be used when using event triggers: click event, change event, keypressed are some of the examples.
Showing or hiding aren't events, so this is why it gets polled like that.To fix and provide the same functionality, turn this into a scope variable.
change the html tag from:
<div ng-show="vm.someFunc()">
to
<div ng-show="vm.someFunc">
And in your controller:
$scope.KeyPressed = false;
$scope.Tags = '';
then create a watch event on what you want to watch for:
//initialize showtag when page loads
$scope.vm.someFunc = $scope.KeyPressed && $scope.Tags !== '';
//watch for any new changes on keypressed
$scope.$watch('KeyPressed', function (newValue, oldValue) {
if (newValue && $scope.Tags !== '') {
$scope.vm.someFunc= true;
} else {
$scope.vm.someFunc= false;
}
}
//watch for any new changes on keypressed
$scope.$watch('Tags', function (newValue, oldValue) {
if (newValue !== "" && $scope.KeyPressed) {
$scope.vm.someFunc= true;
} else {
$scope.vm.someFunc= false;
}
}
Or you can change to a "watchCollection" instead of having multiple watches like:
$watchCollection('[KeyPressed, Tags]', function (newValue) { }
newValue[0] is the value of KeyPressed, and newValue[1] is the value of Tags
Or to go along with the accepted answer and minimize the amount of watches:
$scope.TruthyVal= function () {
return $scope.KeyPressed && $scope.Tags !== '';
};
$scope.$watch('TruthyVal', function (newValue, oldValue) {
if (newValue) {
$scope.vm.someFunc= true;
} else {
$scope.vm.someFunc= false;
}
}
In fact angular does not care what you write in html - function, variable or whatever. It takes expression as string, parse it and calculate its value each digest cycle. So, {{1 + 2}} and {{sum(1, 2)}} and {{1 | sum:2}} are actually doing same job at more or less same speed.
All three ways are legit and do not create memory leaks.
The reason why it is always not recommended to use functions in ng-show is that lot of functions are time consuming, so your digest becomes very slow. And even if your functions are fast, you are not guaranteed that they wont grow up in future.
Related
I'm not too clear why is the $scope object provided as an argument to both the watcher as well as the listener function.
I found this bit of code on GitHub, it's the piece that iterates over the watchers array within $digest:
this.$$watchers.forEach(function(watcher) {
var newValue = watcher.watchFn(self);
var oldValue = watcher.last;
if (watcher.deep && newValue === oldValue) {
deppCompare(newValue, oldValue);
} else {
if (newValue !== oldValue) dirty = true;
}
watcher.listenerFn(newValue, oldValue, self);
watcher.last = newValue;
});
Behind the scenes, when you declare it a watch function in a certain scope, angular parse the expression given in a function:
//The code of a random watcher
$scope.$watch('myVariable', listenerFunction);
//Behind the scenes
$scope.$watch(function(scope){
return scope.myVariable;
}, listenerFunction);
When you register a watcher, the expected behaviour it's that "in THIS SCOPE, i need watch the changes for the variable attached in THIS SCOPE", therefore, behind the scenes the first argument of the $watch() parsed by angular for return the expected variable should be a variable in the scope where a watch function was attached.
I'm getting a very weird behavior and I would like to know if this is the expected one or something should be changed. I have a controller with the following piece of code:
See demo here: http://fiddle.jshell.net/cqofLqwm/
UserSvc.refreshCyclic(20 * 1000);
$scope.$on('userchanged', function onUserChanged(event, user) {
stopWatchScopeUser();
$scope.model.user = user;
watchScopeUser();
});
function watchScopeUser() {
stopWatchScopeUser = $scope.$watch('model.user', function scopeUserChanged(newValue, oldValue) {
if (newValue !== oldValue) {
console.log('user changed');
}
}, true);
}
watchScopeUser();
UserSvc.refreshCyclic gets the user from server every 20 sec., and checks for user object changes. If there are changes, it raises userchanged event.
What is happening here is that even after calling stopWatchScopeUser(), when assigning user to $scope.model.user, the watch expression is called with newValue equal to oldValue, both to the most recent user. (So the condition newValue !== oldValue fails).
If I comment stopWatchScopeUser then the scopeUserChanged is called but newValue is different than oldValue.
It's quite strange that after detaching the watch on model.user, it is still being called. Is this the normal behavior or there is something incorrect? What should I do to definitely deatach watch listener?
How can I execute an AngularJs method from plain javascript?
myApp.controller("someController",["$scope",function($scope){
//Some code was here
$scope.a = valueA;
$scope.b = valueB
});
And a bit later in the code want to execute a function that when valid could execute an IF and if thats the case will execute my AngularJS controller or what ever.
function Clicked(){
if( was clicked correctly ){
//execute my Controller and send some data to add to the DOM
}
I don't want my HTML elements to trigger a function inside your controller.
I know I can build up my canvas from my AngularJS controller and then, since I'm inside the controller, I will have more easy access to it. But, I wanted to know if there is anyway I could executed from the outside of Angular so I could leave my controller light and simple.
Because; what I want to do it's to connect a small game I have in Phaser Framework with some external elements that I have made in AngularJS. Think about it like a TV just that the bottoms and controllers are in Phaser and the Screen it's the Angular part. So basically when something happens in the Phaser part I want to communicated to the Angular part.
Finally I have a solution,
var intermediary;
myApp.controller("someController", ["$scope",function($scope){
intermediary = function(fn){
var phase = $scope.$root.$$phase;
var value;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
value = fn();
if(value){
$scope.valIntermediary = value;
}
}
} else {
$scope.$apply(function() {
value = fn();
if (value) {
$scope.valIntermediary = value;
}
});
}
};
$scope.$watch("valIntermediary",function(){
//Do something whit a value var inside my controller
});
}]};
if(intermediary){
intermediary(function(value) {
return { /*Some data when click is true*/}
})
}
I have a situation in which I want to monitor a specific variable.
$scope.$watch('action', function() { /* do something */ }
However, I only want to do something if $scope.id didn't change (if $scope.action changes it is possible that $scope.id has changed too.)
The only solution I can think of is
$scope.$watch('action', function() {
if(idHasChanhed === false){
/* do something */
}
idHasChanged = false;
});
$scope.$watch('id', function() { idHasChanged = true }
However, I was wondering if angular has a better solution than this, and I don't know if this solution will always work ( is the order in which the $watches are executed random !?)
My solution :
$scope.$watch('[action,id]', function() {
// called when action or id changed
});
Info : The string concatenation action + id will fail.
var action,id; // variables you need to watch
$scope.$watch('action + id', function() {
// actions
});
$watchGroup This function is introduced in Angular1.3. This works the same as $watch() function except that the first parameter is an array of expressions to watch.
Use $watchGroup
$scope.$watchGroup(['action', 'id'], function(newVal, oldVal) {
//write your code here
});
I am designing a Javascript-based Ohms law calculator (voltage, resistance, current) using knockout.js.
I want the ability of the user being able to select what is calculated, , e.g. voltage, resistance, or current, given the other two parameters, via radio buttons.
So my question is, can you change a ko.observable into a ko.computed and vise versa, after ko.applyBindings() has been called?
My initial attempts say no, I have tried this and slaved over the non-working code for hours, trying to get it to work.
You can't do it that way, but you can make all of them read/write ko.computeds that store a "shadow" value when written to and return that value when read from if they aren't the selected quantity (and return a calculated value if they aren't)
You dont even need a writable computed for this like ebohlman suggests
A simple demo
http://jsfiddle.net/K8t7b/
ViewModel = function() {
this.selected = ko.observable("1");
this.valueOne = ko.observable(1);
this.valueTwo = ko.observable(5);
this.result = ko.computed(this.getResult, this);
}
ViewModel.prototype = {
getResult: function() {
if(this.selected() === "1") {
return this.valueOne() - this.valueTwo();
}
return this.valueOne() + this.valueTwo();
}
};
ko.applyBindings(new ViewModel());
edit: hm, if you want the result to be presented in the correct value textbox you need to make them writable computed like ebohlman suggets
As ebohlman mentioned, the vital thing I was missing was shadow-variables, and the use of separate read/write procedures (a recently added feature to knockout) for ko.computed.
The code for one of the three variables is:
this.voltageS = ko.observable();
this.voltage = ko.computed({
read: function () {
if(this.calcWhat() == 'voltage')
{
console.log('Calculating voltage');
if(this.currentS == null)
return;
if(this.resistanceS == null)
return;
this.voltageS(this.currentS()*this.resistanceS());
return(this.currentS()*this.resistanceS());
}
else
{
console.log('Reading from voltage');
return this.voltageS();
}
},
write: function (value) {
console.log('Writing to voltage');
this.voltageS(value)
},
owner: this
});
I have created a JSFiddle here, which demonstrates being able to switch between which variable is calculated.
Another key part to this code is that on read, if it did happen to be the selected variable, as well as calculating it from the other two, I also had to write this result back to the shadow variable. This preventing some of the variables from mysteriously dissapearing/reappearing when the selected variable was changed.