Difference between isolate scope variables and attr for angularjs directives? - angularjs

Given:
scope:{
suit: '#',
value: '#'
},
I can access these values in my directive template with {{suit}} or {{value}}
I can also access them in my link function with attr.suit or attr.value.
What happens when I declare class = "{{suit}} {{value}}" in the template and also do elem.addClass(attr.suit) and elem.addClass(attr.value) at the same time?
It seems that the {{}} value takes precedence and overrides what I do in the link function. Can anyone explain why that is? Also, what is the better practice?

At least one consideration is if you expect the values to change in the same directve. Can 'suit' value can be x, and later be changed to y?
If you expect the 'suit' or 'values' to change, considering you're not manipulating them, I'd use them in the template.
ng-class="{{suit}} {{value}}"
(ng-class has better support for interpolations)
If you don't expect 'suit' or 'values' to change, you can remove them as an isolated scope variables since it defines a binding that you don't need, and use elem.addClass(yourAttribute) as you've already written.

Related

what's the meaning of '=?<name>' in angularJS directive isolate scope declaration?

Does the question mark after equals have special meaning?
Example:
scope: {enumMapping: '=?enumMapping'}
I understand '=?' but this is first time I see it. Anyone explain for me.
The question mark (?) means that the value is not required, so 'enumMapping' can be undefined.
The notation 'enumMapping: '=?enumMapping' can be abbreviated to 'enumMapping: '=?' since the name of the attribute is equal to the internal property.
Instead, when the html attribute used it's different from the internal property, it is required to specify it, eg. 'enumMapping: '=?myExternalAttr'
<my-directive my-external-attr=someValue></my-directive>

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

2-way binding for directive not working

I have a custom Angular directive, and it has an isolated scope having a value base mapped as 2-way binding
base="="
From the parent scope I pass base to directive as
and in the directive controller I am modifying this base value. The base is not just an object but a json structure having arrays within itself. I transverse to few fields inside base and then change those field value. For example one field is changed from 0 to 1.
Now, In another method in parent controller, I pick this base from original JSON, from where it was passed to directive. I expect the values changed in directive to be reflected in original JSON object.
Unfortunately that's not happening. I guess 2-way binding prefixes should make it possible.
I am sorry, since the directive template is too big, I am not currently uploading it. I will try uploading a miniature version later if possible.
In the meanwhile, Am I going wrong somewhere, Is there specifically something that needs to be done.?
A working fiddle illustrating this might help. I googled out and found something known as $broadcast and $emit. I am not sure how to use them in this case, and also I have never used them before.
Expecting little help guys..
Edit:
return {
scope: {
parent: '='
item: '=',
},
controller: 'ctrlname',
templateUrl:'tplname',
link: function (scope, element, attr) {
}
in html something like:
<li ng-repeat= l in originalJSON>
-----------something here------
<li ng-repeat= base in l>
---------something here-------
<li ng-repeat="x in base.y">
<div layout="row" layout-wrap directive-name parent="base" item="item" ></div>
</li>
</li>
</li>
Sorry it was '='
Just to add one more important info. In the parent controller, I am trying to access the originalJSON and looking for modified field in the base field inside it.
Can't you share the base object using services/factories. Also is the parent scope a directive?
The issue you have is multiple directives binding two ways to the same object.
This line appears several times (equal to the number of items in base):
<div layout="row" layout-wrap directive-name parent="base" item="item" ></div>
So you have several two way bindings between parent and base. These are all sitting in a for loop which will be watching base for any changes and updating the loop as necessary. I imagine that this could easily cause issues in your code.
Perhaps, you can bind to x instead?
Edit to clarify (as requested by comment):
You have your 'base' object which is being updated, this is using two way binding on multiple directives. Thus, you have:
base object 'a' being kept in sync with object 'b' in directive 1.
base object 'a' being kept in sync with object 'c' in directive 2.
base object 'a' being kept in sync with object 'd' in directive 3.
and so on in your for-loop. Then, as your directives are being processed, you have some logic to update b,c and d - which in turn you expect to update a. These events will probably all be firing at the same time. Each time they do fire, your for loop, which is looping over elements in a, recognises that 'a' has changed and says 'lets calculate the results of this for loop again'. Then each of the directives are going to be created again - perhaps firing off some more logic to change a.
There is not enough information in your question to tell that this is the problem, but hopefully you can see from my description that race conditions could be problem here. Binding to x in your for-loop instead (and having a one way binding to the base object if it is needed) should be a safer way of achieving what you want.
# is not a two way binding, for two way binding you need to use =
scope: {
"twoway": "=" // two way binding
},

Correct usage of & binding in directives

This has come up for me in answers and generated some discussion and I'd like to figure out if I'm way off base or if there is a "more angular" way to accomplish my goal.
When I write a directive that is going to use an isolated scope, a question that comes up is always =, & or #.
Generally, people always think of & as a way to pass functions to directive. The documentation describes it as:
a way to execute an expression in the context of
the parent scope. If no attr name is specified then the attribute name
is assumed to be the same as the local name. Given and widget definition of scope: {
localFn:'&myAttr' }, then isolate scope property localFn will point to
a function wrapper for the count = count + value expression. Often
it's desirable to pass data from the isolated scope via an expression
to the parent scope, this can be done by passing a map of local
variable names and values into the expression wrapper fn. For example,
if the expression is increment(amount) then we can specify the amount
value by calling the localFn as localFn({amount: 22}).
I use it for passing functions quite a bit, but I also use it in cases where I want to pass an expression that returns a non-string, and I don't want my directive to be able to modify values in the parent scope. In other words, I use it as:
a way to execute an expression in the context of the parent scope
So if I do not need two-way binding, and the expression is not a string, I will do this:
.directive('displayObject', function() {
scope: {
value: '&=displayObject'
},
template: '<div ng-repeat="(k, v) in value()">{{k}}: {{v}}</div>',
replace: true,
...
});
The directive usage would be:
<div displayObject="someScopePropertyOrExpression"></div>
= isn't ideal here because I do not need two way binding. I don't want my directive modifying the value in the parent scope and I don't want the watch required to maintain it.
# isn't ideal because it interpolates the attribute, so value would always be a string.
::someScopePropertyOrExpression doesn't work because I want the directive template to reflect changes in someScopePropertyOrExpression if it changes.
In each discussion, it's always brought up that
ng-repeat="(key, value) in value()"
sets up a watch - the problem is that = and the template together set up two - one that is wholly unnecessary.
Several times when I've suggested this pattern it's been called a "hack", or a misuse of &, and even "ugly".
I don't think it is any of these, but if it is, then my specific question is what is the alternative?
I've searched quite a bit to prove you wrong in our discussion here, but I think you are right - this is the only way to set up a one-way (from parent to isolate scope) binding to an actual model.
In that aspect "&" is superior to "=", because, as you noted, "=" sets up a watch on the parent's scope whereas "&" does not.
One thing - plunker 1 - (that I finally managed to uncover) that hints on it being a hack is that it does not play nice with Angular's one-time binding foo="::name":
"=" will honor the one-time binding (it sets up a watch on the parent and removes it before the next digest), whereas "&" would not:
<input ng-model="name">
<div two-way="::name">
<div one-way="::name">
The other thing - plunker 2 - is that "&" allows to pass local variables, and they may shadow outer variables:
<input ng-model="name">
<div one-way="name">
But in the directive if you did something like: "{{oneWay({name: 'fooo'})}}", it will cause it to display "fooo", rather than take the value of name from the outer scope.
It's a bit like shooting yourself in the foot, yes, but it does hint that this usage might be slightly outside of its original intent.

directive scope two way binding not working using "="

i have this very simple directive.
you can find the code here
In the code,
i have used '#' for link in the scope.
I am able to get it correctly.
But this is not two way binding, so i tried to do it with "=" in scope.
This part does not seems to reflect in my template. I tried to do see if the link variable
is present in scope,it seems to be undefined.
Should this directive be placed inside a controller?
What is that i am missing in my code.
You seem to be missing the difference between the # and = bindings. While the 2 might look similar those are fundamentally different ways of bridging "directive world" with the "page world".
Firstly, let's start with the similarities: both types of binding allows you to pass data from a page that is using a directive to the directive itself (directive intenal scope). But this is where similarities end, and the list of differences goes like this:
= is the 2-way data binding that can cross page / directive world in both ways: from a page to a directive and from the directive scope to the page scope. # on the other hand only allows you to pass data from a page to the directive and not from the directive to the page.
= binding allows you pass data defined on scopes - that is - any JavaScript variable (primitives, arrays, objects). # is different and is passing data through a DOM attribute. As such those attributes are restricted to Strings only.
given the above, the = and # are also triggered differently from the page that is using a directive: for = we need to pass an expression that points to data defined on the scope
while with # we are going through the DOM and need to use the interpolation directive ({{foo}}) to access data available on the scope.
After all those explanations you can see that using = in the directive definition we need to pass an expression so if you do this: <mydirective link="link1" group="main"></mydirective> you are effectively saying: pass to the directive a value of the link1 variable defined on a scope. Since such variable is not defined you are passing undefined to the directive.
So, if you intend to pass a constant (which I assume you want to do), you will need to write:
<mydirective link="'link1'" group="main"></mydirective>
Here is a working plunk: http://plnkr.co/edit/M3qL4MdmoWjTWzZGkwz0?p=preview
One thing most people forget is that you can't just declare an isolated scope with the object notation and expect parent scope properties to be bound. These bindings only work if attributes have been declared through which the binding 'magic' works. See for more information:
https://umur.io/angularjs-directives-using-isolated-scope-with-attributes/

Resources