AngularJS : Difference between the $observe and $watch methods - javascript

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?

$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.

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

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.

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

Related

Running into infinite digest cycle while binding to function that has $http inside

I need to generate http link for a tag. For that reason I put in html line
My link
in controller it defined as:
$scope.getLink = function(inputUrl){
$http.get(inputUrl).success(function(data){/*.....*/});
}
Why AngularJS ends up in infinite cycle? What is the right design?
As explained in another answer, watched expressions are evaluated on every digest and the resulting value is compared to their previous value - dirty checking. If there is a change, another iteration of the digest starts because a change in one value might cause a change in another.
If there is a circular dependency (including, the circle of one, i.e. the same expression is different every time), it results in an infinite loop that Angular stops after 10 iterations.
Specifically, your getLink function's return value is a promise (the return value of $http), and Angular bindings do not "wait" on a promise.
What you want to do is to kick start the $http call and in its handler assign the return value to a ViewModel property that would be bound to <a>:
function getLink(){
$http.get(inputUrl)
.success(function(data){
$scope.url = data.data;
});
}
You can call getLink, for example, when your controller runs.
In the View you just bind url to ng-href (not href) attribute:
<a ng-href="url">My Link</a>
Once you add an Angular expression in View like {{expression}}, it would be added to watch list of current scope.
Angular uses a mechanism called dirty checking to archive two way binding. Each time some specific events happen, Angular will go through the watch list to check whether the watched value has changed or not, this action is called as digest loop.
Here some specific events consist of user input, model change, $http requests finish, etc. As you are using a function getLink in the expression, each time when Angular trigger a dirty check/digest loop, this function will be executed once again to check whether its return result has changed.
The problem is, the function getLink here is a $http request, after it's executed, Angular will triggered another round dirty check ,which will execute this function again ... Bang, it's an infinite loop.
Conclusion: do not add any $http call in angular expression.

AngularJS - ngModel cannot be assigned to function

My question is very common one. I saw many different cases of people with similar question to mine, but I still does not have my answer.
[My goals]
I want to implement a standalone directive to encapsulate div consisting of label(span) and value(input). There are some specifics that worth mentioning here:
My directive aims to preserve the value of the "input" into my page controller score which is an ancestor of the scope my directive works with. Hence with the proper amount of ".$parent" I can access the desired scope for my ngModel assignment.
My directive does not know the name of the placeholder it shall use to preserve the user input as. This placeholder is coming as an HTML property to my directive.
[My approach]
I am using the directive compile phase to manipulate the directive's HTML (mainly adding properties to directive's template HTML). In the pre-link phase I want manipulate the scope that directive is using in order to add the directive's placeholder to my desired scope and assign it some default value if needed. (I guess I can achieve that with ng-init instead of messing with the scopes, but i believe that is irrelevant to my question).
[What I did]
As I explained above I have a function (determinePlaceholderName()) that returns a string which is the exact value I want to assign to ngModel. For example:
$parent.$parent.$parent.placeHolderName
I want to assign that function to input's ng-model attribute assuming that Angular will treath it as an expression, evaluate it and produce what I expected. Here is how I assign the ng-model property:
compile: function(cElem, cAttrs) {
//Some non relevant code here ....
$(cElem[0]).find("div input").attr("ng-model", "determinePlaceholderName()");
}
Basically what I expect is this to be treated like:
$(cElem[0]).find("div input").attr("ng-model", "$parent.$parent.$parent.placeHolderName");
[About determinePlaceholderName()]
This function I define in the link function of my directive:
link: function ($scope) {
$scope.constructValuePlaceholderName = function() {
//Some logic here.
return result;
}
[My Problem]
And finally what my problem is.
Angular result in an error explaining to me that
<input ng-model="myFunc()">
is non-assignable expression as documented.
[My Question]
How can I dinamically set ngModel if function assignment is forbidden by Angular??
Thank you for your time!
I hope this is what you're looking for:
http://plnkr.co/edit/TojgLNx3TUbmszhfs8Ui?p=preview
Using isolated scope, you can pass the name of the model, and the directive itself is not aware of the scope the model actually sits on or its name, but is able to use it as is and give it to ngModel.
The plunker also provides alternatives in case you don't want to use isolate scope or want to use a function.

How can I bind ngModel through multiple directives with isolate scopes in AngularJs?

I am trying to bind to a property via ngModel in a layer of directives 3 levels deep. This would be fine, except the middle level contains a ng-if which I believe creates a new scope. The binding is lost at this point.
I have created a jsfiddle to explain the situation:
http://jsfiddle.net/5fmck/2/
Note that it works if the ng-if directive is removed, but I am using ng-if instead of ng-show for performance reasons
Does anyone know how I can get the original ngModel to update from the 'inputDirective' template in the fiddle?
Simple :3
Just remember, that child scope is created = use reference to $parent :)
<div ng-if='someCondition'>
<span>In Wrapper</span>
<input-directive ng-model='$parent.ngModel'></input-directive>
</div>
http://jsfiddle.net/5fmck/3/
// upd
As I know you need to use reference to $parent only if ngModel is primitive, not object.
I suspect this is due to the nature of how scopes inherit from each other, and so you should use objects on the scope, and not primitives, and always pass the objects into directives via attributes if it's to be then to be used in another scope. So instead of:
$scope.test = "test";
and
<wrapper-directive ng-model="test" some-condition="true">
use:
$scope.userInput = {
test: "Test"
}
and
<wrapper-directive user-input="userInput" some-condition="true">
Which can be seen at http://jsfiddle.net/4RBaN/1/ (I've also changed all-but-one of the ngModels to another custom attribute, as if you're not using ngModel specific things, like ngModelController, or integration with ngForm, then I think better to KISS).
The reason is that if you have 2 scopes in a parent/child relationship (via prototypical inheritance), such as the one created by ngIf, if you execute (or if Angular executes):
$parentScope.test = 'test';
$childScope.test = 'My new value';
Then $parentScope.test will still equal 'test'. However, if you use objects:
$parentScope.userInput = {test: 'test'}
$childScope.userInput.test = 'My new value';
Then $parentScope.userInput.test will then equal 'My new value'.
The standard quote from (whom I believe was) the original author of Angular, is "If you haven't got a dot in your model, you're doing it wrong". There are other questions on SO about this, such as If you are not using a .(dot) in your AngularJS models you are doing it wrong?
Edit: If you do want to use ng-model, and always pass a primitive to the directive, and not an object, then there is a way that allows this. You'll need to:
Always ensure there is a dot in the model that is passed to ngModel, in every directive. Each directive therefore needs to create a new object on its scope in its controller (or linking function). So the template for your 'middle' directive will be:
<input-directive ng-model='userInput.test'>
And in its controller:
$scope.userInput = {
test: $scope.ngModel
}
Where ngModel is bound to the value specified in the directive's attributes:
"scope" : {
"ngModel" : "="
}
Set up manual watchers in each directive, so that changes to the model from the inner directives get propagated back up the chain:
$scope.$watch('userInput.test', function(newTest) {
$scope.ngModel = newTest;
});
This can be seen at: http://jsfiddle.net/zT9sD/ . I have to say, it feels a bit complicated, so I'm not quite sure I can recommend it. Still, (in my opinion) better than using $parent, as introducing a new scope can quite easily happen as things get more complicated, and then you'll have to use things like $parent.$parent.

Angular JS: get ng-model on ng-change

I have the following HTML
<select ng-model="country" ng-options="c.name for c in countries" ng-change="filterByCountry"></select>
That is beeing fed by the following object with a list of countries
$scope.countries = [{name:Afeganistão, country:AF}, {name:África do Sul, country:ZA}, name:Albânia, country:AL}, {name:Alemanha, country:DE}, {name:Andorra, country:AD} ...];
When I change my dropdown value I was expecting my model ($scope.country) to be updated, inside filterByCountry function, but it isn't. What am I missing here?
The ng-change handler is fired before the ng-model is actually updated. If you want filterByCountry to be fired every time $scope.country changes (rather than just when the dropdown changes), you should use the following instead:
$scope.$watch('country', filterByCountry);
I always find it more useful to react to changes in my $scope rather than DOM events when possible.
Just for anyone else coming here,
ng-change is actually called after the model value has been set.
Why?
Let's see when is it called.
From the angular source code, ng-change is just an attribute directive with this directive definition object (DDO).
{
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
ctrl.$viewChangeListeners.push(function() {
scope.$eval(attr.ngChange);
});
}
}
From this we see that the ng-change directive is very simple. All that ng-change='<expr>' does is add a function to the end of $viewChangeListeners that evaluates <expr> via $scope.$eval.
OK ... so when are the ViewChangeListeners called?
Well, if we look at the documentation for ngModel.NgModelController:
the new value will be applied to $modelValue and then the expression specified in the ng-model attribute. Lastly, all the registered change listeners, in the $viewChangeListeners list, are called.
So the viewChangeListener for the ngChange will be invoked after the value is applied to $modelValue. Hence the callback will be invoked after the model as been set.
Also note that this behavior is the same in all versions of angular. The definition for ng-change hasn't changed since v1.2.
As James Lawson pointed,
ng-change is actually called after the model value has been set.
And if you still asking
Now why am I seeing the opposite behavior?
Then you should know that the $scope is prototypical inherited from its parent, and if your scope variable is just a simple type (string, boolean, etc..) then it will be overwritten in child scope to value that was set by ng-model directive
To see where the child scope is created (on what DOM element) you can open dev tools and look for class="ng-scope" on element

Difficulty w/ Directives with optional scope values and setting with literals in html

New to angular. Trying to understand the proper way to do things
I am trying to create a directive that will run custom logic when a user clicks on a link before moving to the next page. However, before I can even get to the custom logic I'm having difficulty setting up the directive. I want to be able to:
A) Have certain attributes on the directive be optional (set to different defaults in certain cases)
B) Have a user be able to pass a string literal OR a variable from the controller's scope into the directive's attributes to be used on the directive's scope.
My Problems:
1.) If I use string literals in the directive's attributes, the corresponding $scope property is undefined. I have to access it through the $attrs. This isn't the worst thing but seems like a wrong practice to have to check the $scope.Prop and if it is undefined check $attrs.Prop.
2.) Also, this answer seems to say I need to use single quote when using string literals in the attributes but that behavior does not work in my example.
3.) When I have certain attributes on the directive not set, the default value I set on the $scope in the 'controller' function on the directive is not reflected when rendered. I cannot figure out why.
Code in Plnkr
Any help would be appreciated!
Here is a version where all three cases work:
The literals work without anything special
The scope values work using the usual {{ expression }} syntax
Defaults are provided in the directive's template using this syntax href='{{strHref || 'default href'}}' which I found in this thread.
Here is the full directive definition:
return {
restrict: 'E',
replace: true,
scope: {
strHref: '#link',
strText: '#displayText',
},
template: '<div><div>variables: {{strHref || \'default href\'}}, {{strText || \'default text\'}}</div> result: {{strText || \'default text\'}}<span ng-show="blnIsBackButton"> (Return)</span></div>',
};
Link : http://plnkr.co/edit/RB6shHI0xYa1FkP4eXDy
Scope & can be used for literals /expression. Scope = for setting up 2 way binding and scope = for method call.
You can set default value for property in controller if you want
I think directive name has to be lower case. Can someone confirm this? If i use pageLink it does not work but works for pagelink. (note 'L'). Same for property.
There are some nice videos about scope in http://www.egghead.io/.
I created this in Plunker which uses all 3 scope isolation types

Categories

Resources