Using angular variables inside non-angular attributes - angularjs

Something I am not understanding about angular is the consistency behind the usage of braces.
say I want to pass in an angular var into an ng-click function:
ng-click="getComments('{{post.Id}}')"
this looks ok in the HTML. but when I click, the parameter is actually still '{{post.Id}}' even though in the html code it was replaced by angular.
also say I want to do this:
<customDirective customAttribute="{{post.Id}}_postID" ></customDirective>
this throws a parsing error and I don't know why. here is the error:
Syntax Error: Token 'post.Id' is unexpected, expecting [:] at column 3 of the expression [{post.Id}}_postID] starting at [post.Id}}_postID].
all the above code is within an ng-repeat.

In the case of ng-click="" whatever is passed between the quotation marks is code, here all variables defined as $scope.variable are simply available as variable, so working code would be
ng-click="getComments(post.id)"
As to the second part, when writing your own directive you specify the attributes to be passed to it like so:
scope: {
attrA: '#',
attrB: '='
}
attrA will bind as a string while attrB will bind as an object, or whatever type variable it may be. For details see this SO answer to ensure you are using the right binding for your use-case.
So your second code sample should read:
<customDirective customAttribute="{{post.Id}}+'_postID'" ></customDirective>
as all text inside the brackets should be seen as code as used when defining any other JS variable.
As to the whole conundrum of when to use which braces in AngularJS, here is a great SO answer on the topic.
Edit:
I have tried out some alternatives for your directive problem, this one works but doesn't update after value changes if that matters:
<customDirective customAttribute="post.Id+'_postID'" ></customDirective>
Though I would suggest that you are approaching a problem with the wrong solution, given that you aren't just interested in figuring out Angular's binding system.
The best solution would be to edit the string in teh directive, or bind an object referencing a function to ensure all changes are propagated:
$scope.post = function(){
return {
id: $scope.someVar.id + "_id"
};
};

Related

Angular HTML Expressions

I am learning angular js and have now a question where I couldn't find the right answer yet.
in the template HTML, you can use expressions to show the scope variables or call scope functions. But I see all the time different versions of it.
{{name}} shows the variable and binds it
{{::name}} the same thing but without binding
userdirective="{{::key}}" But what is the difference here?
ng-if="::field.sortable" With ng-if they are not using {{ but with there userdirective they do?
userdirective="{condition:isActive(route.name),mdColors:{color:'primary'}}" And then there is the last one with just one {. Thats when you create an object.right?
Maybe someone can help me to understand all of it.
Thank you very much for your time. Pat
{{name}} as you say is two-way data-binding
{{::name}} one way databinding
userdirective="{{::key}}" is the interesting case. This statement uses one-way binding into the userdirective ... which means after the $digest it just says userdirective="someValue"
So the userdirective gets that value as a plain value. Now I would have to test it but in the scopepart of the directiive it should say # so it gets read as a string and not as a expression.
The last one is simply as any JSON you build
{ name: value?true:false }
setting value according to conditions that angular evaluates, with a bit of magic involved :D
hope that helps
{{ anything here}} - That is angular expression interpolation. Angular interpolation - here you can find more about that. Basically idea that it interpolate anything you will put inside those brackets. So if you will put expression with some calculations or just variables related to current scope it will convert all variables to their values and apply calculations.
For instance: {{scopevar1 + scopevar2}} in case this variables has some values, let it be 1 and 2, as result we will see 3.
:: - This mean one time binding. For instance {{::scopevar1}} it will be interpolated once and will not check for changes of scopevar1, always stay as first value. Even if scopevar1 will change every second, the value in template will be the same. Angular Expressions - here you can find some live examples and more information.
userdirective="{{::key}}" - This case is nothing more then assigning dynamic value to your directive. UserDirective expectes to get a simple value, but we have it inside our scope, so we need to say: Hey, angular please interpolate scope variable - key, but only once, so my directive will get value, and will not looking for updates of key. And angular does it with pleasure!
userdirective="{condition:isActive(route.name),mdColors:{color:'primary'}}"
The last case is when your directive expects to get some kind of specific JSON. And we don't want to build it inside of controller. It is sometimes easier to do such things in the tempalte. So we put specific object with two properties: condition, mdColors. And saying that first property assigned to result of function, and second one is simple object {color:'primary'}.
That's it!
{{var}} is a two way binding expression and {{::var}} is a one-way binding expression. expression with :: will not change once set, it is a candidate for one-time binding.
go through : https://docs.angularjs.org/guide/expression for better examples on these
{{name}} is the regular case you will find. You basically print the variable name and update it once it changes.
{{::name}} is the same but your value will not receive updates once it stabilises.
So in the first case, your template updates once name is changed. In the latter, it isn't.
userdirective="{{::key}}" is a one-way one-time binding. Leave the :: out and your directive receives updates if key changes. However, if the directive changes key, it will not update the parent.
ng-if="::field.sortable" is a two-way binding. The changes go both ways. In this case, field.sortable is watched by the directive.
userdirective="{condition:isActive(route.name),mdColors:{color:'primary'}}" is used when you want to build adhoc-objects. A popular case is ng-class as well. You may build this object in the controller as well as you should not put too much logic in your template.
In any case, it is advisable to read the excellent docs https://docs.angularjs.org/guide

Angular multiple directives error on md-datepicker

I'm making a dinamic list of dates. The user can adds all datepickers he wants, but I have to validate that there are not matching dates, all of them have to be different, that's the only requisite.
I've made a custom directive validation and it's triggered correctly, but when I try to use its isolate scope, I just get that error (Multiple directives). Other questions/solutions that I've seen here, propose to delete the isolate scope, but I need it to pass to the directive the array of dates and to be able to compare them with the current selected.
Here is a codepen that reproduces the problem. If you remove the noMatchingDates directive's scope, the error just disappears and you can see and add datepickers properly. I mean this scope:
scope: {
getAllDates: "&allDates"
}
I think that it has to do with this line in docs:
Multiple directives requesting isolated scope.
And it probably also has to do with the md-datepicker which would have more directives using the isolate scope. So, how can I solve this error (and still being able to send the dates list)?
If it can't be solved (keeping the scope) given the nature of the md-datepicker, how can I reach this dynamyc validation? I think it could be done using a controller and the ng-change, but I'm not sure if it would be a proper solution.
Indeed there is no reason for your directive to require an isolated scope. Use isolated scope when your directive is like a reusable "visual component". Your directive is about logic validation and shouldn't prevent another such component.
To fix your problem, you can remove the isolated scope and use your directive in the HTML this way:
<div ... no-matching-dates="overtimeList">
Then in your link function, you can retrieve the value of that attribute this way:
var dates = scope.$parse(attr.noMatchingDates);
This will give you the content of what is bound to no-matching-dates, so in this case it will return overtimeList.
I have never used the ctrl.$parsers.unshift syntax, but it seems that you can also use it to retrieve that value. Simply remove the scope.$parse line that I just gave you and write:
ctrl.$parsers.unshift(function(arrayOfDates) { ... })
This should work as well. Note that in the first approach you need to $watch for changes if you want to run the validation every time.

Angular JS custom directive - Attribute binding to string or scope variable

Is it possible when creating a custom directive to have an attribute which can be a string or two way bound to something on the scope?
So, for example if I have this in my directive declaration:
$scope: {
position: '=?'
}
and then alert it in my link function or controller:
$alert($scope.position);
It works if I actually bind it to something on my parent scope, but if I just put a string in I get undefined unless I use single quotes inside the double quotes. E.g.
<my-directive position="'right'"></my-directive>
That way it evaluates the expression as a string, but It seems ugly. I'd rather be able to use position="right" when I want to give the attribute a string, or use position="{{scopeVariable}}" when I want to bind it to two way bind it to something in the parent controller.
Am I wrong to be using "=?" as the isolate scope binding? Is there a better way to do this? Or should I just get used to using single quotes inside double quotes?
= according to Angular documentation represents two-way binding - it means you have to use quotes.
If you are going to pass string your should use text binding:
$scope: {
position: '#'
}
and than you can pass your variable like
<my-directive position="right"></my-directive>
or
<my-directive position="{{right}}"></my-directive>
but remember that it is one way binding.
If your really need 'hybrid' solution which is sometimes a text binding and sometimes two-way binding your could implement your binding manually
{
scope: {}
link: function(scope,element,attr){
attr['position'] // the value of html attribute, use it directly or evaluate in parent scope
scope.$parent.$eval(attr['position']); //evaluate variable 'right' in parent scope
}
}
The difference in # and = syntax may seem confusing. But it is caused by the fact that the latter uses $parse to transform an expression. And the former uses $interpolate, which accepts expression-flavoured string and transforms it using $parse. So attribute with = is always an expression, and Angular doesn't need {{ }} there (it would be position="{{'right'}}" otherwise). And # isn't limited to single expression or static string (I'm not sure that the manual mentioned this clearly).
This behaviour is buried deep inside $compile, and you can't override it in directive, $parse will throw an error because of invalid expression before anything.
To overcome this you need to reimplement = binding in controller or link. All we have to do is to $parse an expression against $scope.$parent, establish proper watcher and remove it on $destroy.

Trying to understand how AngularJS $parse service works

I am trying to understand what angularjs $parse service does. I have read the official documentation at https://docs.angularjs.org/api/ng/service/$parse but that's not really helping. Searching online did not render any good examples.
Any help will be appreciated.
I was going to explain but could not do a better job than this post
"$parse takes an expression, and returns you a function. When you call the returned function with context as first argument. It will execute the expression with the given context."
In the simplest form: It's main purpose is for example to access some tags click function with the right context inside a directive without tight coupling so you can execute it (maybe with some extra params).
The purpose of $parse from my POV is to let us evaluate a "property" from a given $scope. The result of calling $parse is "property", for example:
my controller
$scope.author.name = "Hello World";
somewhere else under the same controller:
var property = $parse("author.name");
Property Getter: property($scope); in this case it is evaluated against the same scope.
Property Setter: property.assign($scope,'Felipe'); assigns a new value to the author name.
$scope give us the context where to evaluate or search for the "property".
I have found them useful when creating directives to still maintain the directive decoupled from the controller but still needing to interact with objects present in the controller.
$parse takes in a string and returns you a function. Below is a simple example of it in action
http://plnkr.co/edit/nicdbwVL2ZZbljZy2Z9S

angularjs : logging scope property in directive link function displays undefined

I have this basic plnkr which just implements a basic "Hello, X" directive.
In the link function I am logging scope.name but I get undefined? Why is it so? Shouldn't it log the value of name property in console?
This is a known "problem" where interpolation of # attributes happens after linking function is invoked. There is a pull request open to change this issue but it is not clear if this one is going to be merged.
In the meantime a way of getting an interpolated value is by observing an attribute like so:
attrs.$observe('hello', function(changedValue){
console.log(scope.name);
});
And the plunk: http://plnkr.co/edit/Lnw6LuadTLhhcOTsPC8w?p=preview
So, at the end of the day this is a bit confusing behavior of AngularJS that might be changed in the future.
Pawel is right (https://stackoverflow.com/a/14552200/287070) but I wanted to add that the problem is that any attribute that contains {{}} interpolation will be set to null in the attrs parameter during the link function as the first $digest since the compilation has not yet run to evaluate these.
The fact that # bindings are null in linking functions is just a symptom of this.
Currently there is no real fix, since we can't start running $digests in the middle of the compilation process. So $observe (or $watch) is the only real way to get hold of these values.
For those in 2015 who are reading this post, please note that the way Angular handles "#" attributes has changed.
Angular 1.2 onwards, interpolation occurs prior to the invocation of the linking function.
An excellent post on this topic is present here.

Resources