383

I know that both Watchers and Observers are computed as soon as something in $scope changes in AngularJS. But couldn't understand what exactly is the difference between the two.

My initial understanding is that Observers are computed for angular expressions which are conditions on the HTML side where as Watchers executed when $scope.$watch() function is executed. Am I thinking properly?

Adam Bubela
  • 9,433
  • 4
  • 27
  • 31
Abilash
  • 6,089
  • 6
  • 25
  • 30

4 Answers4

613

$observe() is a method on the Attributes object, and as such, it can only be used to observe/watch the value change of a DOM attribute. It is only used/called inside directives. Use $observe when you need to observe/watch a DOM attribute that contains interpolation (i.e., {{}}'s).
E.g., attr1="Name: {{name}}", then in a directive: attrs.$observe('attr1', ...).
(If you try scope.$watch(attrs.attr1, ...) it won't work because of the {{}}s -- you'll get undefined.) Use $watch for everything else.

$watch() is more complicated. It can observe/watch an "expression", where the expression can be either a function or a string. If the expression is a string, it is $parse'd (i.e., evaluated as an Angular expression) into a function. (It is this function that is called every digest cycle.) The string expression can not contain {{}}'s. $watch is a method on the Scope object, so it can be used/called wherever you have access to a scope object, hence in

  • a controller -- any controller -- one created via ng-view, ng-controller, or a directive controller
  • a linking function in a directive, since this has access to a scope as well

Because strings are evaluated as Angular expressions, $watch is often used when you want to observe/watch a model/scope property. E.g., attr1="myModel.some_prop", then in a controller or link function: scope.$watch('myModel.some_prop', ...) or scope.$watch(attrs.attr1, ...) (or scope.$watch(attrs['attr1'], ...)).
(If you try attrs.$observe('attr1') you'll get the string myModel.some_prop, which is probably not what you want.)

As discussed in comments on @PrimosK's answer, all $observes and $watches are checked every digest cycle.

Directives with isolate scopes are more complicated. If the '@' syntax is used, you can $observe or $watch a DOM attribute that contains interpolation (i.e., {{}}'s). (The reason it works with $watch is because the '@' syntax does the interpolation for us, hence $watch sees a string without {{}}'s.) To make it easier to remember which to use when, I suggest using $observe for this case also.

To help test all of this, I wrote a Plunker that defines two directives. One (d1) does not create a new scope, the other (d2) creates an isolate scope. Each directive has the same six attributes. Each attribute is both $observe'd and $watch'ed.

<div d1 attr1="{{prop1}}-test" attr2="prop2" attr3="33" attr4="'a_string'"
        attr5="a_string" attr6="{{1+aNumber}}"></div>

Look at the console log to see the differences between $observe and $watch in the linking function. Then click the link and see which $observes and $watches are triggered by the property changes made by the click handler.

Notice that when the link function runs, any attributes that contain {{}}'s are not evaluated yet (so if you try to examine the attributes, you'll get undefined). The only way to see the interpolated values is to use $observe (or $watch if using an isolate scope with '@'). Therefore, getting the values of these attributes is an asynchronous operation. (And this is why we need the $observe and $watch functions.)

Sometimes you don't need $observe or $watch. E.g., if your attribute contains a number or a boolean (not a string), just evaluate it once: attr1="22", then in, say, your linking function: var count = scope.$eval(attrs.attr1). If it is just a constant string – attr1="my string" – then just use attrs.attr1 in your directive (no need for $eval()).

See also Vojta's google group post about $watch expressions.

Red15
  • 755
  • 1
  • 5
  • 17
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 5
    Great answer! Do you have idea why `ng-src/ng-href` use `attr.$observe` instead of `scope.$watch` then? – okm May 21 '13 at 10:17
  • @okm, since they don't create an isolate scope ([source code](https://github.com/angular/angular.js/blob/d551d72924f7c43a043e4760ff05d7389e310f99/src/ng/directive/booleanAttrs.js#L360)), they have to use `$observe` because the attribute values contain `{{}}`s. – Mark Rajcok May 25 '13 at 23:27
  • 5
    +1 For the AngularJS Pope! Every time I search Stack for some info on my latest Angular problem, I inevitably end up reading @MarkRajcok accepted answer. – GFoley83 Jul 08 '13 at 04:14
  • 1
    Thanks for a great post. scope.$eval(item) is really helpful. If item is a json string, it convert to an json object. – bnguyen82 Aug 16 '13 at 09:16
  • 1
    Could you compare scope.$eval and scope.$evalAsync, I sometimes use them when I want to pass in object expressions as directive parameters. Like
    . It allows me to put all the configuration parameters into one attribute.
    – CMCDragonkai Aug 21 '13 at 03:11
  • @CMCDragonkai, $eval evaluates an expression (on the current scope) immediately, whereas $evalAsync will evaluate the expression "[later](http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync)", but before the browser renders, which is sometimes useful if you need to wait for Angular to make some DOM updates before evaluating the expression. See also http://stackoverflow.com/a/17239084/215945 and http://stackoverflow.com/a/17303759/215945. – Mark Rajcok Aug 21 '13 at 13:06
  • @MarkRajcok - Thanks for your excellent answer. If I understand correctly, for directives with isolated scope, `$observe` and `$watch` can be used interchangeably. Is that right? Since both observers and watchers are checked every digest cycle, can I safely say that `$observe` and `$watch` have no difference in performance? – tamakisquare Sep 07 '13 at 09:57
  • 5
    @tamakisquare, they are interchangeable when using the `@` syntax. I believe there is no performance difference (but I haven't looked at the actual source code). – Mark Rajcok Sep 09 '13 at 12:56
  • @MarkRajcok - many thanks. This was so much clearer than any other source of info I've read. Thank you again. – Dave Quick Jan 09 '14 at 22:28
  • @MarkRajcok this is amazing stuff, it does take a while to get your head round though – Nikos Feb 26 '14 at 11:42
  • I found some interesting cases in the following gist, where watch was required for an attribute.. https://gist.github.com/CMCDragonkai/6282750 – djabraham Jan 02 '15 at 05:45
  • 1
    @MarkRajcok and @tamakisquare, [from the docs](https://docs.angularjs.org/api/ng/service/$compile), it might suggest that $observe is more performant and also more accurate, at least when using it with `$compile`: "Use `$observe` to observe the value changes of attributes that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to `undefined`." – adam0101 Jun 17 '15 at 14:44
  • @MarkRajcok This is amazing, but it looks like AngularJS has changed a bit since 2013. I updated your Plnkr to pull in Angular 1.4.5 (without making any other changes to your code) ... http://plnkr.co/edit/ix4gUPRAkeLglLkAsBTt?p=preview ... The output to the console is different ... Could you may be talk about what has changed? – Niko Bellic Nov 04 '15 at 00:48
26

If I understand your question right you are asking what is difference if you register listener callback with $watch or if you do it with $observe.

Callback registerd with $watch is fired when $digest is executed.

Callback registered with $observe are called when value changes of attributes that contain interpolation (e.g. attr="{{notJetInterpolated}}").


Inside directive you can use both of them on very similar way:

    attrs.$observe('attrYouWatch', function() {
         // body
    });

or

    scope.$watch(attrs['attrYouWatch'], function() {
         // body
    });
Abhay Dixit
  • 998
  • 8
  • 28
PrimosK
  • 13,848
  • 10
  • 60
  • 78
  • 3
    Actually, since every change gets reflected in `$digest` phase, it is safe to assume that the `$observe` callback will be called in `$digest`. And `$watch` callback will also be called in `$digest` but whenever the value is changed. I think they do the exact same job: "watch the expression, call callback the value changes". The keyword difference is possibly just syntactic sugar for not confusing the developer. – Umur Kontacı Feb 14 '13 at 16:34
  • 1
    @fastreload, I totally agree with your comment.. Nicely written! – PrimosK Feb 14 '13 at 20:22
  • @fastreload... Thanks for the wonderful explanation. If I understood correctly, Observers are for Angular Expressions. Am I right? – Abilash Feb 15 '13 at 03:17
  • @PrimosK: adding you for my previous comment. – Abilash Feb 15 '13 at 03:18
  • 2
    @Abilash observers are for watching dom attributes, not just expressions. So if you change the attribute value by yourself, it would be reflected in the next digest cycle. – Umur Kontacı Feb 15 '13 at 06:42
3

I think this is pretty obvious :

  • $observe is used in linking function of directives.
  • $watch is used on scope to watch any changing in its values.

Keep in mind : both the function has two arguments,

$observe/$watch(value : string, callback : function);
  • value : is always a string reference to the watched element (the name of a scope's variable or the name of the directive's attribute to be watched)
  • callback : the function to be executed of the form function (oldValue, newValue)

I have made a plunker, so you can actually get a grasp on both their utilization. I have used the Chameleon analogy as to make it easier to picture.

vdegenne
  • 12,272
  • 14
  • 80
  • 106
  • 2
    It is pretty obvious about its usages. But why was the question. Mark has summed it up beautifully. – Abilash May 31 '15 at 03:49
  • 3
    I think the params might be switched - it appears to pass newValue, then oldValue to attrs.$observe() . . . – blaster Oct 14 '15 at 20:23
0

Why is $observe different than $watch?

The watchExpression is evaluated and compared to the previous value each digest() cycle, if there's a change in the watchExpression value, the watch function is called.

$observe is specific to watching for interpolated values. If a directive's attribute value is interpolated, eg dir-attr="{{ scopeVar }}", the observe function will only be called when the interpolated value is set (and therefore when $digest has already determined updates need to be made). Basically there's already a watcher for the interpolation, and the $observe function piggybacks off that.

See $observe & $set in compile.js

Niko
  • 462
  • 1
  • 6
  • 13