AngularJS custom attribute does not evaluate first time directive runs - javascript

HTML
<a custom-attr='{{ controller.object.value }}' data-ng-model='controller.object.value'>
Angular directive
.directive('customAttr', function () {
return {
require: 'ngModel',
controller: 'ControllerName',
controllerAs: 'cName',
link: function (scope, el, attr, ctrl) {
el.on('click', function ($event) {
if (ctrl.$viewValue && attr.customAttr) { // breakpoint
}
})
}
}
})
Goal:
to see the correct value in attr.customAttr the first time the directive runs.
Description
Stopping at a breakpoint on the if statement inside of the directive's link function, I expect to see a boolean value. I have verified the boolean value is correct in the model using $log.log(). Unfortunately, the first time the directive runs, attr.customAttr evaluates to a string of the reference to the model value ('controller.object.value' in the debugger), and then on subsequent iterations of the directive it evaluates correctly to the boolean. I tried removing the curly braces from the attribute, and I just get an unchanging empty string.
What can I do that will cause the model value to evaluate correctly the first time?
Note: I have done a similar version of this before with a numeric value without problem. The key difference appears to be that the working version is on an input element, and has both ngModel and ngValue attributes.

Consider using
attrs.$observe('customAttr', function() {
scope.customAttr= scope.$eval(attrs.customAttr);
});
to properly double bind .attrs (and thus, probably resolve your issue ) to your directive.
More info in this answer and in the docs.

As it turns out the controller and controllerAs properties were having an effect on this. I removed it, but then decided I would rather have the controller so instead I used isolate scope to evaluate the object instead of reading it from the attr property.

Related

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

Angular js access `ctrl.$modelView` in main flow of directive

Updated with three methods that work, and the original one that does not
I have made an angular js directive, and I am trying to access the ctrl.$modelValue. It does not work in the main flow.
I have three potential solutions, all of which have drawbacks.
Method 1 does not work as I would hope, and I can't find any other property available in the directive directly accessible in this way.
Method 2 works because it waits until the current flow is complete, and then executes in the next moment. This happens to be after the angular js lifecycle is complete, and the controller seems to be hooked up to the model at this point. This does not seem ideal to me, as it is waiting for all execution to finish. If it is possible, I would prefer to run my code as soon as the controller is linked to the model, and not after all the code in current flow completes.
Method 3 works well, accessing the model from the $scope, and determining what the model is from the string representation accessed on the attrs object. The drawback is that this method uses eval in order to get hold of the addressed value - and as we all know, eval is evil.
Method 4 works, but it seems like an overly complex way to access a simple property. I can't believe that there is not a simpler way than the string manipulation, and while loop. I am not confident that the function for accessing properties is 100% robust. At least I might like to change it to use a for loop.
Which method should I use, or is there a 5th method that has no drawbacks?
DEMO: http://jsfiddle.net/billymoon/VE9dX/9/
HTML:
<div ng-app="myApp">
<div ng-controller="inControl">
I like to drink {{drink.type}}<br>
<input my-dir ng-model="drink.type"></input>
</div>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('inControl', function($scope) {
$scope.drink = {type:'water'};
});
app.directive('myDir', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, element, attrs, ctrl) {
// Method 1
// logs NaN
console.log({"method-1": ctrl.$modelValue});
// Method 2
// on next tick it is ok
setTimeout(function(){
console.log({"method-2": ctrl.$modelValue});
},0);
// Method 3
// using eval to access model on scope is ok
// eval is necessary in case model is chained
// like `drink.type`
console.log({"method-3": eval("$scope."+attrs.ngModel)});
// Method 4
// using complex loop to access model on scope
// which does same thing as eval method, without eval
var getProperty = function(obj, prop) {
var parts = prop.split('.'),
last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
}
console.log({"method-4": getProperty($scope,attrs.ngModel)});
}
};
});
There are quite a few alternatives, some better than others depending on your requirements, e.g. should you be notified if the view value changes, or the model value, or are you happy with the initial value.
Just to know the initial value you can use either of the following:
console.log('$eval ' + $scope.$eval(attrs.ngModel));
console.log('$parse ' + $parse(attrs.ngModel)($scope));
Both $eval and $parse end result is the same, however $eval sits off $scope, where $parse is an Angular service which converts an expression into a function. The returned $parse function can then be invoked and passed a context (usually scope) in order to retrieve the expression's value. In addition, if the expression is assignable the returned $parse function will have an assign property. The assign property is a function that can be used to change the expression's value on the given context. See $parse docs.
If you need to be informed when the model value changes, you could use $watch, however there are better ways when dealing with ngModel. If you need to track changes to the the model value when the change occurs on the model itself, i.e. inside your code, you can use modelCtrl.$formatters:
ctrl.$formatters.push(function(value){
console.log('Formatter ' + value);
});
Note that $formatters are only called when the model value changes from within your code, and NOT when the model changes from user input. You can also use $formatters to alter the model view value, e.g. convert the display text to uppercase without changing the underlying model value.
When you need to be informed of model value changes that occurs from user input, you can use either modelCtrl.$parsers or modelCtrl.$viewChangeListeners. They are called whenever user input changes the underlying model value:
ctrl.$viewChangeListeners.push(function(){
console.log('$viewChangeListener ' + ctrl.$modelValue, arguments);
});
ctrl.$parsers.push(function(value){
console.log('$parsers ' + value, arguments);
return value;
});
$parsers allows you to change the value from user input to model if you need to, where $viewChangeListeners just lets you know when the input value changed.
To sum up, if you only need the initial value, use either $eval or $parse, if you need to know when the value changes you need a combination of $formatters and $parsers/$viewChangeListeners.
The following fiddle shows all these and more options, based on your original fiddle:
http://jsfiddle.net/VE9dX/6/
Instead of using the native eval use the $eval function on the $scope object:
console.log($scope.$eval(attrs.ngModel))
See this fiddle: http://jsfiddle.net/VE9dX/7/

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

passing paths in an angularjs directive

I have a directive which i'd like to pass a path
<mydirective api="/my/tomcat/server?asd=123&efg=456">
However I get "Lexer Error: Unexpected next character". I assume some encoding needs to take place. Can someone advice?
I'm not sure why you are getting the lexer error without seeing your code, if you could update the post, we may be able to tell why that happens. In the meantime, there are a few ways to retrieve an attribute value in your directive. Not all will work with a string literal.
1) The # isolate scope binding: This parses the value and will work as is with your HTML. The parse actually happens later so this value will not be immediately available in the directive's internal methods (i.e. link, etc.) and is best used in the directive's template method. Ex.:
scope: {
api: '#'
}
2) The = isolate scope binding: This will work if you wrap your expression in single quotes because it evaluates the attribute as an expression. Ex.:
scope: {
api: '='
}
And in your HTML (notice the single quotes):
<mydirective api="'/my/tomcat/server?asd=123&efg=456'">
3) Attribute Evaluation: This allows you to evaluate the attribute string value directly from the directive's internal methods and will work as is with your HTML. Ex:
link: function(scope,element,attrs){
console.log(attrs.api);
}
You can read more about directives in the AngularJS directive doc here.
Angular try to evaluate the expression in the api attribute, you have to surround it with quotes :
<mydirective api="'/my/tomcat/server?asd=123&efg=456'">

AngularJS : Difference between the $observe and $watch methods

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

Categories

Resources