Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

newValue = oldValue when $scope.$watch used in a directive within ng-repeat #11565

Closed
williamzhao87 opened this issue Apr 13, 2015 · 8 comments
Closed

Comments

@williamzhao87
Copy link

Problem is reproduced in the following plunker:

http://plnkr.co/edit/mNN4UuDJZdZZWr52fYar

The $scope.$watch will fire, but newValue is always equal to oldValue, so when we use the following check, it doesn't update.

$scope.$watch("data", function(newVal, oldVal){
if (newVal !== oldVal) {
// data is updated, redraw the directive in my case
// however newVal is always the same as oldVal
}
})

@frfancha
Copy link

It isn't a problem or a bug: the data never "changes" in your scenario, it just "appears".
When it does, the watcher is called for the first time, and when a watcher is called for the first time, angular always provides as old value the same value as the first observed value.
If you add another $timeout to change the data after the first creation, your watcher will be called with old different of new.
I highly recommend reading the first chapter (the first one is freely available) of "Building your own angular JS book" from Tero Parviainen, after that watchers will have no more secret for you.

@pkozlowski-opensource
Copy link
Member

@frfancha thnx so much for helping with the issues here, this is very much appreciated!

@stianlp
Copy link

stianlp commented May 20, 2015

@frfancha Are you sure this is really what is going on? I have the same issue. In my case it is not ngRepeat, but some cached data returned from a promise (so the data isn't changed). The first time $watch fires in the directive newValue is equal to oldValue.

I cannot find an explanation to this problem anywhere else. Do you have any reference to angularjs documentation or somwhere else this issue is discussed?

@frfancha
Copy link

@stianlp
It is NOT a problem.
When and only when angular first calls a watcher, angular doesn't know what was the old value.
Instead of passing null or undefined in the old value, it gives us the "new" (in fact the current) value. This is better, as receiving null or undefined wouldn't allow the watcher to make the distinction between a true old value being null and the first time we are called. This is the only call with old=new you will receive.
You always receive this call after adding a watcher, even if according to your code the watched value has never changed.
By this way of working, the simple fact to set a watcher let you always know the current value of the observed variable. The watcher is called with the current value in old and new after setting the watcher, and then at any change.

@stianlp
Copy link

stianlp commented May 20, 2015

@frfancha
Ok, I agree, it is not a "problem", but I just wonder why I haven't seen any other questions or discussions on this topic elsewhere? It seems to me that using if(newValue === oldValue) return; is some kind of best practice (everyone use it), but this line of code created the issue for me.

I found a post on $observe (http://stackoverflow.com/questions/14876112/difference-between-the-observe-and-watch-methods/14907826#14907826) on SO and in my specific case $observe works like a charm.

I posted a question on SO about this specific issue because I am curious why $watch is used more commonly than $observe.

http://stackoverflow.com/questions/30356803/watch-vs-observe-when-newvalue-oldvalue-angularjs

EDIT: Also, why should one use if(newValue === oldValue) return;. It can't imagine it would increase the perfomance that much? If the watcher fires, in almost all situations the value has changed.

@frfancha
Copy link

Using
if(newValue === oldValue) return;
is not best practice, it depends of what you want to achieve.

Suppose you want to maintain varB = 2 * varA

You can do:
[A]
varB = 2 * varA;
and then put a watcher on varA, with the line if (n===o) return; to react on changes of varA and recompute varB = 2 * varA.

or: [B]
Only put the watcher on varA computing varB = 2 * varA; without the line if (n===o) return;

Scenario [B] is easier and has also the following advantage: in scenario [A] you must be sure that between the initialisation of varB and the first call to the watcher nothing has modified varA, otherwise you miss the first change.

Scenario [A] is better when computing varB from varA is a heavy process, and you know that the initial value varB is already inline with varA. Then you avoid a first useless recomputation of varB.

Again, all this is really a LOT easier to master and understand after reading the first chapter (the first one is freely available) of "Building your own angular JS book" from Tero Parviainen

@stianlp
Copy link

stianlp commented May 21, 2015

@frfancha
"Scenario [A] is better when computing varB from varA is a heavy process, and you know that the initial value varB is already inline with varA. Then you avoid a first useless recomputation of varB."

This is not about varA being inline with varB. It is simply about when does varA appear in the directive. This is the first time I've seen this, and it is probably because varA doesn't change as in other cases where if(n===0) return; works just fine.

In my case varA does not change, but the binding that gets varA into the isolated scope does (from ' ' to a number). This is, as you explained in your first answer, simply the value appearing in the directive. The value must appear at some point, but you can never be sure when it will appear, can you? Therefore we must check if the binding into the directive has changed.

So, I found $observe and was just wondering why there are no examples out there with the following:

            attrs.$observe('varA', function(newValue, oldValue) {
                if (newValue == oldValue) return;
                doCalculation();
            });

Because it seems to me, that this is what we want in most cases?

@toxaq
Copy link

toxaq commented Jun 6, 2016

I'm with @stianlp in that most examples I've seen use this technique (newValue == oldValue).

I've just looked at the ng-bind source code and it seems the canonical way of doing these watches is simply to check if the newvalue is defined

if angular.isDefined(newValue){/*action*/}

I can't understand how a watch could fire first time with the oldvalue being the same as the newvalue but it is what it is. Although ng-bind is binding on compile, not link...

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants