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
},
Related
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
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.
I have a directive that is repeated by an ng-repeat.
<div ng-repeat="p in people">
<p>{{p.name.first}}</p>
<slider-box
title-text="{{p.name.first}}"
body-text="Some body text"
img-src="{{p.photo}}"
more-link="#"></slider-box>
</div>
</div>
If I put a <p>{{p.name.first}}</p> before the directive, the p shows off a first name, but the slider-box doesn't seem to have access to p and therefore comes up blank as there's no data being given to it.
It probably isn't an async issue, as there are boxes being created, so angular knows that there is something to ng-repeat , and the <p> is getting filled in.
I've made an an example site here that shows an example of the directive with dummy data (just strings) and then the repeated ones are below that.
A very similar question has been asked here but no code examples were posted and the trail seems to have gone dead.
You should pass the attributes into the scope directly in the directive declaration.
In your directive declaration, instead of just having scope: {} , have something like scope:{ imgSrc:"#" }. It will pass the value of the attribute directly inside the isolated scope of your directive.
Take a look at this short tutorial for a better explanation.
I have a directive with an isolate-scope (so that I can reuse the directive in other places), and when I use this directive with an ng-repeat, it fails to work.
I have read all the documentation and Stack Overflow answers on this topic and understand the issues. I believe I have avoided all the usual gotchas.
So I understand that my code fails because of the scope created by the ng-repeat directive. My own directive creates an isolate-scope and does a two-way data-binding to an object in the parent scope. My directive will assign a new object-value to this bound variable and this works perfectly when my directive is used without ng-repeat (the parent variable is updated correctly). However, with ng-repeat, the assignment creates a new variable in the ng-repeat scope and the parent variable does not see the change. All this is as expected based on what I have read.
I have also read that when there are multiple directives on a given element, only one scope is created. And that a priority can be set in each directive to define the order in which the directives are applied; the directives are sorted by priority and then their compile functions are called (search for the word priority at http://docs.angularjs.org/guide/directive).
So I was hoping I could use priority to make sure that my directive runs first and ends up creating an isolate-scope, and when ng-repeat runs, it re-uses the isolate-scope instead of creating a scope that prototypically inherits from the parent scope. The ng-repeat documentation states that that directive runs at priority level 1000. It is not clear whether 1 is a higher priority level or a lower priority level. When I used priority level 1 in my directive, it did not make a difference, so I tried 2000. But that makes things worse: my two-way bindings become undefined and my directive does not display anything.
I have created a fiddle to show my issue. I have commented out the priority setting in my directive. I have a list of name objects and a directive called name-row that shows the first and last name fields in the name object. When a displayed name is clicked, I want it to set a selected variable in the main scope. The array of names, the selected variable are passed to the name-row directive using two-way data-binding.
I know how to get this to work by calling functions in the main scope. I also know that if selected is inside another object, and I bind to the outer object, things would work. But I am not interested in those solutions at the moment.
Instead, the questions I have are:
How do I prevent ng-repeat from creating a scope that prototypically inherits from the parent scope, and instead have it use my directive's isolate-scope?
Why is priority level 2000 in my directive not working?
Using Batarang, is it possible to know what type of scope is in use?
Okay, through a lot of the comments above, I have discovered the confusion. First, a couple of points of clarification:
ngRepeat does not affect your chosen isolate scope
the parameters passed into ngRepeat for use on your directive's attributes do use a prototypically-inherited scope
the reason your directive doesn't work has nothing to do with the isolate scope
Here's an example of the same code but with the directive removed:
<li ng-repeat="name in names"
ng-class="{ active: $index == selected }"
ng-click="selected = $index">
{{$index}}: {{name.first}} {{name.last}}
</li>
Here is a JSFiddle demonstrating that it won't work. You get the exact same results as in your directive.
Why doesn't it work? Because scopes in AngularJS use prototypical inheritance. The value selected on your parent scope is a primitive. In JavaScript, this means that it will be overwritten when a child sets the same value. There is a golden rule in AngularJS scopes: model values should always have a . in them. That is, they should never be primitives. See this SO answer for more information.
Here is a picture of what the scopes initially look like.
After clicking the first item, the scopes now look like this:
Notice that a new selected property was created on the ngRepeat scope. The controller scope 003 was not altered.
You can probably guess what happens when we click on the second item:
So your issue is actually not caused by ngRepeat at all - it's caused by breaking a golden rule in AngularJS. The way to fix it is to simply use an object property:
$scope.state = { selected: undefined };
<li ng-repeat="name in names"
ng-class="{ active: $index == state.selected }"
ng-click="state.selected = $index">
{{$index}}: {{name.first}} {{name.last}}
</li>
Here is a second JSFiddle showing this works too.
Here is what the scopes look like initially:
After clicking the first item:
Here, the controller scope is being affected, as desired.
Also, to prove that this will still work with your directive with an isolate scope (because, again, this has nothing to do with your problem), here is a JSFiddle for that too, the view must reflect the object. You'll note that the only necessary change was to use an object instead of a primitive.
Scopes initially:
Scopes after clicking on the first item:
To conclude: once again, your issue isn't with the isolate scope and it isn't with how ngRepeat works. Your problem is that you're breaking a rule that is known to lead to this very problem. Models in AngularJS should always have a ..
Without directly trying to avoid answering your questions, instead take a look at the following fiddle:
http://jsfiddle.net/dVPLM/
Key point is that instead of trying to fight and change the conventional behaviour of Angular, you could structure your directive to work with ng-repeat as opposed to trying to override it.
In your template:
<name-row
in-names-list="names"
io-selected="selected">
</name-row>
In your directive:
template:
' <ul>' +
' <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
' <a ng-click="setSelected($index)">' +
' {{$index}} - {{name.first}} {{name.last}}' +
' </a>' +
' </li>' +
' </ul>'
In response to your questions:
ng-repeat will create a scope, you really shouldn't be trying to change this.
Priority in directives isn't just execution order - see: AngularJS : How does the HTML compiler arrange the order for compiling?
In Batarang, if you check the performance tab, you can see the expressions bound for each scope, and check if this matches your expectations.
I want to write 'edit in place' directive in angularjs.
I want that directive is reusable, therefore I have following requirements on the directive:
it must be an attirbute that can deocorate any element, that makes sense (div,span,li)
it must support edit button, clicking on that will change set ot displayd elements into input fileds. Typically properties of one object, e.g. contact (number, name)
I disocvere trickery behaviour of scope visibility in the directive that can be seen in this fiddle http://jsfiddle.net/honzajde/ZgNbU/1/.
Comenting out in the directive: template and scope -> contact.number and contact.name are displayed
Comenting out in the directive: scope -> contact.number only is displayed
Not commenting out anything -> nothing is displayed
=> when both are commented out just adding template to the directive makes it render contact.number even though template is not used.
I am asking what are the rules of the game?
<div>
<div ng-controller="ContactsCtrl">
<h2>Contacts</h2>
<br />
<ul>
<li ng-repeat="contact in contacts">
<span edit-in-place="" ng-bind="contact.number"></span> |
<span edit-in-place="" >{{contact.name}}</span>
</li>
</ul>
<br />
<p>Here we repeat the contacts to ensure bindings work:</p>
<br />
<ul>
<li ng-repeat="contact in contacts">
{{contact.number}} | {{contact.name}}
</li>
</ul>
</div>
</div>
var app = angular.module( 'myApp', [] );
app.directive( 'editInPlace', function() {
return {
restrict: 'A',
//scope: { contact:"=" },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});
app.controller('ContactsCtrl', function ( $scope ) {
$scope.contacts = [
{ number: '+25480989333', name: 'sharon'},
{ number: '+42079872232', name: 'steve'}
];
});
You are running into problems because you are misusing angular.
First, a directive should be self-contained, but you are pulling functionality out of it, which makes it less universal and less reusable. In your code, you have functionality in the DOM and in the controller that belongs in the directive. Why?
Second, it's also unclear from your markup and javascript specifically want you want to accomplish when all these pieces are strung together.
Third, in most cases, directives should have their own isolated scope, which is done by declaring a scope object with attributes it should bind. You shouldn't be passing an expression (i.e. {{contact.name}}) inside the directive as it will break the binding and your contact will not be updated when the edit-in-place finishes. The proper way is to establish bi-directional binding through an = property on the scope. ng-bind isn't what you want here: that's scope-specific, so we use it inside the directive's scope. As Valentyn suggested, you could do some magic to get around this, but it's not a good idea and it's super-simple to set it up the right way. What's the issue with doing this by an attribute?
This is all bad Ju-ju.
As I pointed out in your other question on this same topic, you must make your directive self-contained and work with angular, rather than against it. Here's an attribute-based version of the fiddle I gave you previously, meeting the first of your requirements. Please let me know what is wrong specifically with this implementation and we can talk about the angular way of fixing it.
Lastly, if you provide further context on what you need in terms of a "button", I'll incorporate that into the fiddle too.
[update]
It is possible to make the directives work your way, but you will run into problems eventually (or right now, it would seem). All components in an angular app (or any app for that matter) should be as self-contained as is feasible. It's not a "rule" or limitation; it's a "best practice". Similarly, communication between directive components can occur through a controller, but it shouldn't. Ideally, you shouldn't reference the DOM in a controller at all - that's what directives are for.
If your specific purpose is a row that is editable, then that is your directive. It's okay to have a lower-level generic edit-in-place directive that the larger directive uses, but there is still the higher-level directive too. The higher-level directive encapsulates the logic between them. This higher-level component would then require a contact object.
Lastly, no, there isn't necessarily a big difference between ng-bind="var" and {{var}}. But that's not the issue; the issue was where that binding takes place. In your example, a value was passed to the directive instead of a bi-directionally-bound variable. My point was that the directive needs access to the variable so it can change it.
Summary: You are coding in a very jQuery-style way. That's great for coding in jQuery, but it doesn't work so well when coding in Angular. In fact, it causes a lot of problems, like the ones you're experiencing. In jQuery, you would, for example, dynamically insert DOM elements, declare and handle events, and manually bind variables all within a single code block, all manually. In Angular, there is a clean separation of concerns and most of the binding is automatic. In most cases, it leads to javascript code at least two-thirds smaller than the jQuery alternative. This is one of those cases.
That said, I have created a Plunker that contains a more sophisticated version of both the edit-in-place as well as a new higher-level directive to incorporate additional features: http://plnkr.co/edit/LVUIQD?p=preview.
I hope this helps.
[update 2]
These are the answers to your new round of questions. They may be good for your edification, but I already gave you the "angular way" to fix your problem. You will also find that I already addressed these questions (in broader strokes) earlier in my original answer as well as in my update. Hopefully, this makes it more apparent.
Question: "Comenting out in the directive: template and scope -> contact.number and contact.name are displayed"
My Reply: When you do not specify a scope, the directive inherits its parent scope. You bound and interpolated the name and number within the context of the parent, so it "works". Because the directive will alter the value, however, this is not a good way way to solve it. It really should have its own scope.
Question: "Comenting out in the directive: scope -> contact.number only is displayed"
My Reply: You bound a scope property of the parent to the "contact.number" directive, so it will get placed inside during the $digest loop - after the directive has been processed. On the "contact.name", you put it inside the directive, which can only work if the directive codes for transclusion.
Question: "Not commenting out anything -> nothing is displayed"
My Reply: Right. If the directive has its own scope (and this one definitely should), then you must use a defined directive scope property to communicate values, as my several code samples demonstrate. Your code, however, tries to use the parent scope in the directive when we explicitly forbid that by using the scope property in its definition.
Summary: While this second update may be informative (and I hope that it is), it doesn't answer the question beneath your questions: how do I use angular components correctly so that the scope I'm using is always what I think it is? My first post and the subsequent update, answer that question.
Here is little bit updated your fiddle, but it need further improvements to meet full list of your requirements: http://jsfiddle.net/5VRFE/
Key point is:
scope: { value:"=editInPlace" },
Some notes: its better to use ng-show ng-hide directivies for visual appearing-hiding instead of changing css classes. Also its better to spread functionality into different directives to have better separation of concerns (check ngBlur directive)
About your confusion of scope check guide about scopes paragraph "Understanding Transclusion and Scopes": Each directive have separate isolated scopes, if you want to have access from directive's template to controller's scope use directive scope binging ("scope" field of directive definition object). And also transcluded elements have a scope of from where you defined transcluding template.
From the first view those isolated scope sounds little bit strange, but when you have good structured directives (note also that one directive can require another and share bindings) you can find it extremly usefull.