How can I get notifications of rootScope changes? - angularjs

I've got a global variable in my rootScope for AngularJS which has properties updated in other various places (outside of angular). For example, lets say the property 'name' is updated on it. It seems like it updates on the root scope fine and after doing an apply or firing a controller function on any child function the view eventually updates, but this is a problem.
How can I get the controllers to update the templates to reflect the rootScope changes immediately?
How can I observe any changes whatsoever on this object, and invoke apply?

You can create a $watch as so:
// Assuming that $rootScope.name exists
$rootScope.$watch('name', function(newValue, oldValue)
{
// You have access to both the newValue
// and the oldValue
});
To $watch for objects, taking this from Angular's Site, a third boolean character is needed
objectEquality (optional) boolean: Compare object for equality rather
than for reference.
// Assuming that $rootScope.obj exists
$rootScope.$watch('obj', function(newValue, oldValue)
{
// You have access to both the newValue
// and the oldValue
}, true);
But know that it is not a good idea to use the $rootScope to store content. You should consider using a service or a factory for that purpose. Read more about this here.

Create a watch for it on rootscope ($rootScope.$watch('varName', function (newValue) {}[,true])).
The varName parameter accepts expressions with '.' for sub-objects and '[]' for array indexes. The third parameter indicates listening fire changes "inside" the object.

Related

Angularjs watch input binding change for Array when using ControllerAs

I have an AngularJs component having bindings to heroes, which is an array. How to watch this input for array changes? I tried $scope.watch("heroes", ...) and $onChanges, but didn't work so far.
bindings: {
heroes: '<',
}
Here is my plunker: https://plnkr.co/edit/J8xeqEQftGq3ULazk8mS?p=preview
The ControllerAs structure needs a special watch expression, since the attributes are not on the $scope.
//This one works and is the best one (> AngularJs 1.5)
$scope.$watch("$ctrl.heroes.length", function () {
console.log("ControllerAs syntax"); // Triggers once on init
});
//This one works as well
var ctrl = this;
$scope.$watch(() => {
return ctrl.heroes.length;
}, (value) => {
console.log("complex watch"); // Triggers once on init
});
See example here: https://plnkr.co/edit/J8xeqEQftGq3ULazk8mS?p=preview
The issue occurs because $scope.$watch by default doesn't deeply watch objects. Which means since you never destroy/recreate your array, the reference doesnt really change therefore $scope.$watch doesnt see any change. If you watched heroes.length, that primitive would change and your $scope.$watch would fire the corresponding listening function. By using $scope.$watch with the true option you are telling the angular engine to deeply watch all properties.
This is pretty intensive to do for large objects because $scope.$watch using angular.copy to track changes
If you were to use $scope.$watchCollection angular would create a shallow copy and would be less memory intensive. So I feel your 3 main options are
Watch heroes.length , add true or use $watchCollection
I feel that using heroes.length would be your best bet, so the code would look like
$scope.$watch('heroes.length',function(){});
The other two options are described below
$scope.$watch('heroes',function(){
//do somthing
},true)
or
$scope.$watchCollection
The benefit of using watchCollection is, that it requires less memory to deeply watch an object.
Shallow watches the properties of an object and fires whenever any of
the properties change (for arrays, this implies watching the array
items; for object maps, this implies watching the properties). If a
change is detected, the listener callback is fired.
The obj collection is observed via standard $watch operation and is
examined on every call to $digest() to see if any items have been
added, removed, or moved. The listener is called whenever anything
within the obj has changed. Examples include adding, removing, and
moving items belonging to an object or array.

In angularJs: what's the different between setting $watch on string variable and setting $watch on Object's key?

In angularJs: what's the different between setting $watch on string variable and setting $watch on Object's key?
The detail scenario is as follow:
$scope.activedMenu = {'id' : '...', 'name' : 'menu1'};
$scope.selectedMenuName = 'menu1';
$scope.$watch('activedMenu.name', function () {...});
$scope.$watch('selectedMenuName', function () {...});
So, my question is what's the different between "$scope.$watch('activedMenu.name', function () {...})" with "$scope.$watch('selectedMenuName', function () {...})"? Any help will be appreciated!
(I think these two ways to set a $watch are equivalence, I refer from the scope develop guide! https://docs.angularjs.org/guide/scope)
Basically $watch need string parameter & search for that parameter inside current scope & placed dirty watch on it.
Watching on activedMenu.name OR selectedMenuName is one as the same thing, 1st one will watch on name property of activedMenu, the 2nd one will watch on selectedMenuName scope variable.
The only difference I think is you are watching on single property so you can use object equality option here which deep watches the object change. It could be possible for you 1st watch but watch string should be activedMenu only
$scope.$watch('activedMenu', function(newVal, oldVal){
//on watch code here
}, true);

$scope.$watch with non existing variable as value

I've been trying to understand $watch function on $scope object. This looks pretty straight forward but the thing that I don't understand is why (on page load) listener function is being executed when I pass a non existing $scope object variable in value function.
$scope.$watch ('nonExistingVariableIdentifier', function () { console.log('Variable value changed'); });
Am I missing something?
The watch runs when it is created.
The full use of a $watch is:
$scope.$watch("nonExistantVariable", function(newValue, oldValue) {
if (newValue == oldValue) {
// First run
}
else {
// After First run
}
})
This is the correct way to differentiate between the initialization and an actual change.
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.
From the Angular JS Docs for $watch - Docs

Accessing ngModel from controller and watch for changes

i have make custom directive which is working good. It's plunker is at http://plnkr.co/edit/GxM78QRwSjTrsX1SCxF7?p=preview
in that directive there is ngModel call deptStation I want access it in controller so that i can use it as parameter in other function to make new array. I also want to watch it also so on every change I can call the function.
<plexus-select items="deptStations" header-text="Select station" text="Select departure..." text-icon="ion-chatbubble-working" text-field="City_Name_EN" text-field2="City_Code" value-field="City_Code" ng-model="deptStation">
</plexus-select>
I tried to write below code but it don't show console log
$scope.$watch('deptStation', function(newValue, oldValue) {
if(oldValue != newValue) {
// perform something
console.log('New Value ' + newValue);
}
I'm not exactly sure about ionic directives, but your issue is probably because one or more of the ionic directives creates a new scope, so just doing ng-model="deptStation" creates a new property in that scope rather than your controller's.
To avoid these issues you should make sure not to bind to primitives, but arrays/objects instead. You should create the property like so (renamed to selectedStation for clarity):
$scope.selectedStation = {value: null};
Then your watch will work:
$scope.$watch('selectedStation.value', function (station) {
console.log('watch', station);
});
http://plnkr.co/edit/96VgPzEXZuzxmHt32Afy?p=preview
As #m59 said though, it seems you'd be better just using two-way binding than ng-model.

Model update detection

I try to implement an autosaving feature for my forms basing on the $watch function of AngularJS. I initialized watching as described below:
var unbindWatcher = $scope.$watch('selectedElement', function(newValue, oldValue) {
(...)
});
The selected element is loaded using the $resource object and its query method. The selectedElement is set then using one element of the list. Fields of the selectedElement are linked with form elements using ng-model attribute.
The problem is that the callback specified in the $watch method is called once before any update is done.
Is it a normal behavior of the $watch method? How to catch when the selectedElement is actually updated?
Thanks for your help.
Thierry
Yes it is expected behavior After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. You can add below line to function
var unbindWatcher = $scope.$watch('selectedElement', function(newValue, oldValue) {
if(newvalue===oldvalue) return;
});

Resources