Angular: Best practices for angular $watch registration - javascript

I'm playing around with handling the query string on page load, and noticed something about $watch.
It looks like it takes some time for $watch to register when it is called - and this process is asynchronous.
I'm finding that I have to deal with race conditions as my $watch does not trigger when it should - that is, values that are set after the $watch declaration do not get evaluated by the $watch.
See this example for an illustration of the problem: http://jsbin.com/kapifezi/24/edit
Does anyone have a best practice around using watches to avoid this type of issue?

As it turns out - after investigating further - I didn't fully understand how $watch worked.
http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/ really helped shed some light on the situation.
As it turns out - simply changing a variable that is being watched won't trigger a digest loop by itself.
The reason $timeout worked was because it "safely" makes a call to $digest which causes all $watch expressions to be evaluated - that is, it was the $digest component of the $timeout that caused the $watch to fire and not the wait of 1000 milliseconds.
Lesson learned.

do you mean value updated inside $timeout??? there will be 1000 miliseconds delay before $scope.val is changed to 1 inside $timeout and after that $scope.val gets updated

Not really got what do you mean but in my controller if I need watch for some var im doing this:
$scope.myValue = "value";
$scope.$watch("myValue", function(val) {
if (val) {
console.out(val);
}
});

Related

forms.$valid showing true, but prints out as false

I have a strange error and I am not sure what's happening.
When I check an object, I can see that the $valid tag is set to true (and $invalid shows false). But when I print out just that tag (such as object.$valid) it prints false (and object.$invalid prints true).
$scope.$watch('ctrl.form', form => {
console.log('The form is set to: ', form);
console.log('The form is valid: ', form.$valid);
});
Does anyone know why this might be happening?
I suspect the issue is, the values are still updating in that function; it is watching for a change in the value, but still hasn't applied the changed values. This can make your code in that handler unpredictable.
Since you mention applying the scope throws the "in progress" error, that seems to be valid (the digest is still working). This code frag checks for an in-progress digest and sidesteps it if necessary.
if ( ! $scope.$$phase ) {
$scope.$apply ();
}
Note that "$$" vars in Angular were "private". You could still access them but you're not "supposed" to. This does however have the benefit of not appearing as randomly vague as a zero timeout.
Alternatively, as you've already seen, a 0 timeout can force an update. That's a hack, but it does work and I've seen it used a few times.
Lastly, one of the lifecycle events may be a better choice for these traces, such that you know you're tracing information in the natural flow of the component, where digests are predictable.
Regarding that lifecycle hook, if you're using 1.5 (the last paragraph seems to fit right here and is probably what you want):
New: $onChanges
This new hook is similar to ng2’s ngOnChanges. It is called whenever one way bindings are updated, with a hash containing the changes objects.
Prior to this hook you sometimes had to use a $watch in order to do some work whenever a value you’re bound to changes. Using this hook makes things clearer and removes the need to introduce a watch and a dependency on $scope.

How to force Angular 1.x Digest loop to run 3 times

This is a bit of a theoretical question, but important in my understanding how the Digest Loop in Angular 1.x works.
I understand why the Digest loop would usually run twice. Once for detecting changes and another time to make sure no watched expression is 'dirty'.
I can also easily concoct an example that puts the loop into an infinite cycle, thus more than 10 times and causing it to throw an exception.
However, what circumstance (with an example) would there be when the loop runs more than twice but less than 10 times? It almost seems like there is logic somewhere inside the loop to do some magic rechecking of expressions or queuing up expressions or something that causes it to always run only 2 times. Once to update all the changed values and once to verify that nothing has changed.
What set of watchers would cause this to execute 3 times, for example.
I keep trying to come up with an example that has a custom watcher do some side effect on some $scope.prop that's watched as well, but the digest loop does not run more than twice.
In other words, if the watcher does a side effect in a limited fashion like this:
if (some limited condition) change another watched $scope.property
Then, the digest loop runs only 2 times.
If the watcher does a side effect without any limits on the condition for when that side effect gets triggered, it comes an infinite loop (understandably). For example:
$scope.someProp++
Is there an example of how you can force the digest loop to run 3 times? It's almost as if it's somehow making sure all the changes occur in the first loop, making the 2nd loop there to check that all props have dirty=false, so it can then stop.
Isn't the 2nd loop to make SURE that nothing has changed as a result of the first loop? If so, it must be possible to trigger the condition where something DID change as part of the first loop but is not detected until the 2nd loop, making loop #3 the final 'clean' one, not just stop at 2nd loop.
Just trying to understand how this whole digest thing works.
Thanks!
A little bit artificial, but hey ;) Euclidean algorithm implemented using $scope.$watch. As you can sometimes there is stop condition, like in this case. When b === 0 then digest cycle stops.
So I guess, any similar case, when change values in scope based on some condition would fire more than 2 but not infinitely.
angular.module('app', []);
angular
.module('app')
.controller('Example', function ($scope) {
$scope.a = 45;
$scope.b = 375;
$scope.$watch('a', function () {
console.log($scope.a, $scope.b);
if ($scope.b !== 0) {
const temp = $scope.b;
$scope.b = $scope.a % $scope.b;
$scope.a = temp;
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.js"></script>
<div ng-app="app" ng-controller="Example">
{{a}}
</div>

Angular.js - Performance implications of binding a model to be watched, using $watchCollection

I'm trying to get a better understanding of a new codebase, and haven't found a ton of info (or maybe I'm not fully understanding what I have read) about the performance implications of some choices that were made. The previous maintainer isn't available for insight.
Each controller is set up to make its data request before being rendered initially using this pattern:
$routeProvider.when('/path', {
resolve: { someMethod: function($q) {
var deferred = $q.defer();
.... do some stuff ...
return deferred.promise;
}
});
This is then injected into the controller - that part makes sense and from my understanding is more performant because you're saving digests by waiting for the data to come through first. A loading state is shown by default and removed when we digest.
The value of the injected param - in this case someMethod - is then assigned to this.someModel.
Throughout the controller, remaining methods and properties are set with this.doStuff = function() { ... }, this.property = 'some string', etc.
In the end, a watch is setup and properties made available to the $scope by setting a $watchCollection like this:
$scope.$watchCollection(angular.bind(this.someModel, function() {
return this;
}), function(newTitle, oldTitle) {
if (newTitle.title !== oldTitle.title)
{
self.updateThings(newTitle);
}
});
I understand that it's binding the context of the watch to the current context of this, then watching to changes on its properties using $watchCollection which will perform shallow reference checks of its top level properties.
Per this article I get that simply using properties in my templates with {{ property }} will cause them to be watched, but in this case they aren't watched until they're bound to the $watchCollection.
Of course this is less expensive than using $watch and passing it true for the optional object equality param, but is it better or worse from a performance perspective than putting things directly onto $scope? Why, specifically?
I've never seen it done this way before, so any readings, insights, or things that could give me a better understanding will be greatly appreciated.
The only real thing I can think of is that the author wanted to keep everything off the scope directly, only populating properties of the controller instance, which will be available on the scope via the property specified in the controllerAs property on the route definition object.
However, when doing it that way, if you want to set up $watches on the scope to be notified of changes to something, you can't, because you don't actually know what property of the scope it will be published on.
You could settle on a convention, such as vm, ctrl or whatever, for the controllerAs value, but that is not ideal and is fragile and not really a concern of the controller code itself. But if you made that concession, you could use $scope.$watchCollection('vm.someModel', ... instead.
One final possible reason is that $scope.$watch() and $scope.$watchCollection() always ultimately end up dealing with a function as the thing they use to get the value of the thing being watched during the digest. If you pass a string, it uses the $parse service under the covers to turn it into a function. If you pass a function directly, no conversion is necessary, so this can be perceived as a minor performance improvement.
The real reason(s) may be one, all or none of these, but they are valid reasons for adopting this approach. I actually admire it for its purity and strict adherence to "controller as", which is the in-vogue strategy encouraged by the Angular team and community at the moment.

Firebase/AngularFire performance issues causing page to become unresponsive? $watch at fault?

I've built a site with angularfire and I've noticed that as the site is left open it begins to lag and eventually freezes the browser.
Is there a way I can trace this to the source to see what's causing it?
It's very likely this double watch function but I can't seem to get the list to sort properly and repopulate when I change .start() and .end() parameters on my $firebase array.
scope.$watch(function(){
scope.list.$watch(updateList);
return scope.list;
}, updateList);
Seems to be related to the nesting. The following works much better. Can anyone please explain why I need to have two $watches setup?
scope.list.$watch(updateList);
scope.$watch(function(scope){
return scope.list;
}, updateList);
As written, this code will recursively establish listeners, increasing the resource usage each time there is a change in scope.
It's unclear why you've chosen to watch both scope and the list's $watch method, but only one of these should be necessary.
list.$watch(function() {
// do whatever needs to be done when the list changes
});
Also, updateList implies that you're changing the list or making copies of it. If you are changing the list inside a $watch event, that's going to trigger more watch events. If you're making copies of the list, those are probably unnecessary.
More context about the use case and end goal could help with specifics.
I think you are misunderstanding how to use $watch. The correct syntax would be.
scope.$watch('list', updateList);
Alternatively,
scope.$watch('list', function(newValue, oldValue) {
if (someConditional) {
updateList()
}
});
See for more details https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch

$interval's invokeApply parameter does not change anything

From the angular's documentation of $interval service:
invokeApply (optional) boolean: If set to false skips model dirty checking, otherwise will invoke fn within the $apply block.
Which can lead us to a conclusion that $rootScope.$apply wouldn't be called if I set invokeApply as false.
However, from the source of $interval I learned that deferred.notify is called each tick, which makes sense. What does not, is the fact that during deferred.notify execution $rootScope.$evalAsync is called, which in order calls $rootScope.$digest. So all the dirty checking still happens. Am I missing something here?
Bug already filed!
https://github.com/angular/angular.js/pull/5903
You are missing nothing

Categories