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.
Related
Is it possible to establish a two-way binding between parent scope and my directive's isolated scope, but without passing it through my directive's attributes?
'=' establishes a two-way, but not literal binding: instead of taking a specified value from the parent's scope directly, it looks for directive's attribute with a such name, then evaluates it, then uses a resulting value as a parent scope variable name.
'#' establishes a literal binding just as I want, but only one-way.
I've also tried '=#' in a hope that Angular.js is clever enough to understand that, but no luck.
I know that I can also use non-isolated scope (scope:true), but inside my code I'm using some techniques that necesarilly require an isolated scope.
Is there some way to do what I want?
Passing a model through the scope between Directive and State considered as a bad practice because it violates Loose Coupling design principal. Means that it potentially makes Directives lesser reusable, have side effects in the scope, and introduces unclear behavior. The same reasons are true when we are talking about the global state in JavaScript which considered as a bad practice too(but still available for some rare and specific cases).
I would recommend to take a closer look in one-way bound # attributes, though. Because, actually, they provide a way to pass values forward and backward as well keeping the control on the Directive side. Don't be confused with the terms :)
// in directive
var theValueReturnsByController = scope.myOneWayAttr({theValuePassedToController: 1});
alert(theValueReturnsByController === 2);
// in controller
scope.oneWayAttrHandler = function(theValueReceivedFromDirective) {
theValueReturnedToDirective = theValueReceivedFromDirective + 1;
return theValueReturnedToDirective;
};
In template:
<my-directive my-one-way-attr="oneWayAttrHandler(theValuePassedToController)"></my-directive>
It would be a bad design if you could call it directly. Why not pass it through bindings? You explicitly use that method.
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
},
I know "&" binding used to pass the method (expressions) to directives isolate scope, so the directive will able to execute it when needed.
Many times I need to "pass" the same expression from my main controller, more than one level deep, to nested directive (2-3 levels). This why on my own, I don't like to use "&" for that purpose. For me, sending "callbacks" using "=" bindings works much better. But this is not a question.
The question is:
What for, I can use "&" in addition to passing functions?
Can I have something like this: my-directive-click="clickCount +=1"?
& is more about allowing you to deal with expressions, and more importantly (in my eyes) allows you to place parameters into the calling parent's scope.
For instance if you have:
scope: {
something: '&'
}
and then in that directives template you could do:
<select ng-model="selection" ng-change="something({$item: selection})" ...>
The caller/user of this directive than can ACCESS $item in the expression passed to something, i.e. $item is placed on its scope.
e.g.
<my-dir something="myOwnVar = $item + 1"></my-dir>
here is a plunker with an example of this, including chaining (multiple nested calls of a & expression):
http://plnkr.co/edit/j4FCBIx0FVz4OT0w50bU?p=preview
In reality, the & is meant as one-way-data-binding.
So = is two-way-data-binding which means changes done on the directive will persist to the original object.
# is just a string.
And & is special. The thing is that it creates a getter for your value, in the case of an invoked function, the getter actually calls the function. I tend to do this on the DDO:
.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
getParameters: '&?params'
}
So in this way, the value bound to the scope is getParameters (so it's clear is a getter) but on the directive element you will only refer it as params:
<my-directive params="ctrl.params">
Your question is vague though and even though you may be able to do what you were asking, I think it would be best to do that inside the directive rather than the way you proposed.
I hope this helped.
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/
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.