angularjs: $watch two variables and do something if only one changes - javascript

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
});

Related

AngularJs variable scope outisde function

I am getting data using socket into angularJS controller.
$rootScope.list1= '';
socket.emit('ticker', symbol);
socket.on('quote', function(data) {
$rootScope.list1 = angular.fromJson(data.substring(3));
//I can't call any function from here to use this variable
});
I want to use updated list1 variable in my code below but I am not able to as it's value is coming as ''.
This can be because I am trying to access it in my script prior to it being updated i.e. prior to the response received.
Is there any way where I can use the updated value of variable list1 in my code below.
Edit 1
As suggested by #Manish Singh in one of the answers, I tried $rootScope.watch.It is reflecting new value for above peice of code but not for code given below.
$rootScope.peers = [];
industrysearch.searchindustry(indussymbol).then(function(results) {
industrysearch.searchpeers($scope.industrystock[0]._source.industry).then(function(results1) {
for (var number_stocks = 0; number_stocks < results1.length; number_stocks++) {
$rootScope.peers.push(results1[number_stocks]);
}
});
});
$rootScope.$watch('peers', function(newVal1, oldVal1) {
console.log(newVal1); //prints []
});
one is normal variable and one is used as array.I can;t see any other difference.
Edit 2
$watchCollection is use for arrays/collection which I missed.Below code is working.
$rootScope.$watchCollection('peers', function(newVal1, oldVal1) {
console.log(newVal1);
});
Thanks for help!
How about using a watch on $rootScope
$rootScope.$watch('list1', function(newVal, oldVal) {
//Do Something
)};
Try "$applyAsync":
$rootScope.list1= '';
socket.emit('ticker', symbol);
socket.on('quote', function(data) {
$rootScope.$applyAsync(function(){
$rootScope.list1 = angular.fromJson(data.substring(3));
});
});

Angularjs watch not working with array inside of object

I have an object {Client:[],Employee:[],Product:[],Project:[],PayPeriod:[]} in which each array gets pushed and spliced by components through a two way binding. The main controller connects all 5 of the arrays and gives them to another component. In said component I need to watch that binding but no matter what I do it does not work. This is what I have now.
$scope.$watch('ctrl.parameters', ctrl.Update(), true);
ctrl.Update(); is a function and works.
ctrl.parameters does get updated but does not trigger $watch.
It's a bit of a complicated so if you need anything explained butter I can.
ctrl.Update = function () {
$.post("/TrackIt/Query.php?Type=getViaParams&EntityType="+ctrl.entity,{Params:ctrl.parameters},function(Data,Status){
if(Status=="success"){
if (Data.Success) {
ctrl.List = Data.Result.Entities;
} else {
AlertService.Alert(Data.Errors[0],false,null);
SessionService.Session(function () {
ctrl.Update();
});
}
$scope.$apply();
}else{
AlertService.Alert("Something is up with the select options",false,null);
}
},'json');
};
Edit 1 :
Par = {Client:[],Employee:[],Product:[],Project:[],PayPeriod:[]}
5 Components with two way binding = Par.X (these are what edit the parameters)
1 Component with two way binding = Par (I need to watch the binding inside here)
Edit 2 :
<script>
TrackIT.controller('EntryController', function EntryController($scope, $http, AlertService, SessionService, DisplayService) {
$scope.Parameters = {Client:[],Employee:[],Product:[],Project:[],PayPeriod:[]};
$scope.Values = {};
});
</script>
<style>
entity-select{
float: left;
display: inline;
padding: 0 5px;
}
#SelectParameters{
float: left;
}
</style>
<div ng-app="TrackIT" ng-controller="EntryController">
<div id="SelectParameters">
<entity-select entity="'Client'" ng-model="Values.Client" multi="true" ng-array="Parameters.Client"></entity-select>
<entity-select entity="'Employee'" ng-model="Values.Employee" multi="true" ng-array="Parameters.Employee"></entity-select>
<entity-select entity="'Product'" ng-model="Values.Product" multi="true" ng-array="Parameters.Product"></entity-select>
<entity-select entity="'Project'" ng-model="Values.Project" multi="true" ng-array="Parameters.Project"></entity-select>
<entity-select entity="'PayPeriod'" ng-model="Values.PayPeriod" multi="true" ng-array="Parameters.PayPeriod"></entity-select>
</div>
<br>
<parameter-table entity="'Entry'" parameters="Parameters"></parameter-table>
</div>
TrackIT.component('entitySelect', {
templateUrl: "/Content/Templates/Select.html",
controller: function SelectController($scope, $http, AlertService, SessionService) {
var ctrl = this;
ctrl.Options = [];
ctrl.Display = [];
ctrl.Add = function () {
var Display = {'Label':ctrl.Label(ctrl.ngModel),'Value':ctrl.ngModel};
ctrl.ngArray.push(ctrl.ngModel);
ctrl.Display.push(Display);
};
ctrl.Remove = function (Key) {
ctrl.ngArray.splice(Key, 1);
ctrl.Display.splice(Key, 1);
};
ctrl.$onInit = function() {
$.post("/TrackIt/Query.php?Type=getSelectList&EntityType="+ctrl.entity,null,function(Data,Status){
if(Status=="success"){
if (Data.Success) {
ctrl.Options = Data.Result.Entities;
if(ctrl.ngModel==undefined){
if(ctrl.none){
ctrl.ngModel = "NULL"
}else{
ctrl.ngModel = angular.copy(ctrl.Options[0].Attributes.ID.Value.toString());
}
}
} else {
AlertService.Alert(Data.Errors[0],false,null);
}
$scope.$apply();
}else{
AlertService.Alert("Something is up with the select options",false,null);
}
},'json');
};
ctrl.Label = function(Value) {
for (var prop in ctrl.Options) {
if(!ctrl.Options.hasOwnProperty(prop)) continue;
if(ctrl.Options[prop].Attributes.ID.Value.toString()==Value.toString()){
return ctrl.Options[prop].DisplayName;
}
}
};
},
bindings: {
entity:"<",
multi:"<",
none:"<",
ngModel:"=",
ngArray:"="
}
});
TrackIT.component('parameterTable', {
templateUrl: "/Content/Templates/BasicTable.html",
controller: function ParameterTableController($scope, $http, AlertService, SessionService, DisplayService) {
var ctrl = this;
ctrl.List = {};
ctrl.Update = function () {
$.post("/TrackIt/Query.php?Type=getViaParams&EntityType="+ctrl.entity,{Params:ctrl.parameters},function(Data,Status){
if(Status=="success"){
if (Data.Success) {
ctrl.List = Data.Result.Entities;
} else {
AlertService.Alert(Data.Errors[0],false,null);
SessionService.Session(function () {
ctrl.Update();
});
}
$scope.$apply();
}else{
AlertService.Alert("Something is up with the select options",false,null);
}
},'json');
};
$scope.$watch('ctrl.parameters', ctrl.Update.bind(ctrl), true);
ctrl.$onInit = function() {
DisplayService.DisplayTrigger(function () {
ctrl.Update();
});
ctrl.Update();
}
},
bindings: {
entity: "<",
parameters: "="
}
});
There are two problems here.
Problem 1: ctrl is not a property on the scope
After seeing the full controller code, I can see that ctrl is just an alias for this, the instance of the controller which will be published on the scope as $ctrl by default. But you can avoid having to worry about what it is called by instead passing a function instead of a string to $scope.$watch():
// ES5
$scope.$watch(function () { return ctrl.parameters; }, ctrl.Update, true);
// ES6/Typescript/Babel
$scope.$watch(() => ctrl.parameters, ctrl.Update, true);
It's all functions to Angular
You may not be aware that as far as Angular is concerned, it is always calling a function for each watch to get the value to compare. When you pass a string to $scope.$watch(), Angular uses $parse to create a function from that expression. This is how Angular turns strings into executable code in bindings, expressions, and so on.
The function that gets created takes in a single parameter, which is the "context" to evaluate the expression on. You can think of this as which scope to use.
When you pass a function to $scope.$watch() as the first parameter, you effectively save Angular having to create a function for you from the string.
Problem 2: the way you specify the watch listener function
Your ctrl.Update() function is just a function that you want run whenever ctrl.parameters changes.
What you have said in your code of $scope.$watch('ctrl.parameters', ctrl.Update(), true); is:
Do a deep watch (watch changes to any property) on ctrl.parameters, and when it changes, call the result of calling ctrl.Update(), which will be a jQuery promise, not a function.
Instead, you want to pass the ctrl.Update function itself as the second parameter to $scope.$watch(), so it gets called when a change is detected. To do that, just pass ctrl.Update instead of ctrl.Update():
$scope.$watch('ctrl.parameters', ctrl.Update, true);
A Note of Caution
Using ctrl.Update in this particular case will work, because there is no use of this inside that function. For others looking at this answer, note that when you pass a function in this way, the this binding (the "context") is not maintained as ctrl as you might expect. To get around this, use ctrl.Update.bind(ctrl), or just wrap it in a function so it gets called with the correct context: $scope.$watch('ctrl.parameters', function () { ctrl.Update() }, true);.
Use deep/value watches sparingly
You should be very sparing in your use of deep watches in an Angular app (also known as value watches). The reason is that it is a very expensive operation for big objects, as Angular has to do a deep comparison of the object on every digest cycle - traversing through every single property on the entire object, and then, if there is a change, making a deep clone of the object, which again requires traversing every single property to make a completely separate copy to compare against next time.
You can think of a deep watch on an object with n properties as being the equivalent of n shallow/reference watches.
I have a feeling that may be a scarily large number in your situation.
I think the problem is that your watch statement is incorrect. The second parameter to $watch must be a function. The following should work:
$scope.$watch('ctrl.parameters', ctrl.Update.bind(ctrl), true);
Note the use of bind to ensure the this parameter is set appropriately.

javascript - how to watch an object

I have a object and want to run a function if that object changes. I tried using angular $watch, but this only works without setters.
Searching for another solution i found obj.observe but mdn says it's obsolete and not widely supported. so is there any other way to run a function if my object changes?
Edit:
// watch using this doen't work, when update through ng-model
Object.defineProperties(self, {
'myprop': {
get: function () {return _myprop; },
set: function (value) {
_myprop = value;
}
},
I'm not sure what do you mean by $watch "working without setters" .
scope.$watch('name', function(newValue, oldValue) {
console.log('name has been changed: ' + $scope.name);
scope.counter = scope.counter + 1;
});
Just start simple, put the above code into a controller and have a simple field modifying the $scope.name variable. You'll see in the console that the watch is triggered.

How an Angular $watch to indentity how the $scope.value changed?

I have a watch for a variable. $scope.value
I have two possiblility to update my value.
One is from my controller eg..through any services.
Other is any input event can update my $scope.value. By keypress ect..
I need to identify from were the update is occured from my watch.
$scope.$watch('value',function(){
//how to identify from were my actual change occured.
});
There is no built-in or easy or standard way to do this.
You may:
Set a temporary variable to indicate the change source, e.g.
var sourceOfChange;
myService.doSomething().then(function() {
sourceOfChange = 'myService';
$scope.value = ...
});
$scope.$watch('value',function() {
if( sourceOfChange === 'myService' ) {
...
}
...
});
Update different variables per source, watch those variables and do the source-specific processing, and finally update the value, e.g.:
// initialization
$scope.value = ...
$scope.valueFromInput = $scope.$value;
$scope.valueFromService = $scope.$value;
// watching the source-specific values
$scope.$watch('valueFromInput', function(newval) {
doInputSpecificProcessing(newval);
$scope.value = newval;
});
// watch the value and do common processing
$scope.$watch('value', doCommonProcessing);
There can also be other ways

Changing A ko.observable into a ko.computed and vise versa

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.

Categories