two way binding on primitive variables in angularjs directive - angularjs

Trying to have 2 way binding on an AngularJS directive while using primitive objects is not working, for example:
<custom-directive ng-model="variable"></custom-directive>
how can this be achieved?

In order to have 2 way binding in javascript (not just angularjs), we have to pass an object (this is caused by javascript's evaluation strategy - can read more about it here https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing). basically what is happening is that when we pass a primitive variable, its been passed by value and re-created, instead of been passed by reference. only objects are passed by reference.
So this issue can be solved by passing the variable's parent object, for example:
<custom-directive ng-model-name="variable" ng-model-parent="parentObj"></custom-directive>
and then, modifying in object in the directive as following:
parentObj[variable] = "whatever";
this way, we will keep the connection between the variable to the parentObj.
another option would be passing the model with the parent obj, for example:
<custom-directive ng-model="parentObj.variable"></custom-directive>
the dot is an important part of this example. its actually a best practice by angular to always pass variables with the parentObj-dot-property.
for additional information, angularjs actually has a documentation about it https://github.com/angular/angular.js/wiki/Understanding-Scopes

I just realized that if your directive isn't inside an ng-if it will work with primitive bindings. Maybe the problem is that your bind is inside an ng-if. Try to use ng-show instead. Maybe it will work.
Passing the primitive this way:
<custom-directive ng-model="parentObj.variable"></custom-directive>

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

Best practice: Passing attribute parameter through a few descendant directives, or passing controller directly to descendant?

I have a parent directive with variable 'a' on its scope, as scope.a. I would like to pass scope.a to a descendant directive through a child directive.
Is it better practice to create a controller on the parent with a getA() function that can be used directly in the descendant directive, or is it also OK to pass scope.a via attribute to the descendant directive through the intermediate child directive?
Or perhaps there is a different way of doing this that is even better?
I think it depends on the nature of your data. If the intermediate directive has nothing to do with that data and its sole purpose by getting it is to pass it on, then it sounds to me it shouldn't do that.
You can also use a service to store that data and fetch it from your last child directive in a similar way it's done to share data between controllers.
What you said also works, using "require" attribute in the child directive. That's intended to communicate between directives. But is it also possible to use this child directive independent of the (grand)parent directive? If the answer to this question is yes, then you should hold the data in a service to be able to fetch it regardless of the DOM structure. If the answer is no, the I guess this method will do fine.

Angularjs directive: Passing a model reference to an isolated scope

I 'm trying to write a collapsible, reusable calculator directive, that binds to an input field (in the parent scope). This input field itself has a ngModel binding.
When the user presses the equals-button of my directive this parent scope model should be updated. I need to isolate the scope so I can reuse it:
Here is the simplified code and how I would like to use it:
http://plnkr.co/edit/OSOcxydJWh8K520nstAU?p=preview
I tried passing in the values as an attribute. but that does not work because I don't know how to update this attribute inside of the controller(I tried the $attrs service).
So how can I update the model from the directive?
Maybe you're overthinking it, maybe I'm underthinking it. Either way, here's all I did to change yours to make it work:
if ($scope.operator ==='+') {
$scope.field = parseInt($scope.field) + $scope.operand;
}
I uncommented your scope and then I made sure that your controller made reference to the data you had exposed in your scope. That's it.
And here's a working version of your Plunker: http://plnkr.co/edit/btBi3E
You need to use ngModelController. Here's a link with docs, with a handy example:
NgModelController

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/

ngModel doesn't pass data back to parent scope in directive

Related Post, but didn't help:
Scoping issue when setting ngModel from a directive
EDIT: Can I use ng-model with isolated scope? didn't work either.
I got the some problem but in a more complex way I guess. I want to write a pulldown that does not use any input for data saving. I'd rather have the ngModel to take care of it.
http://jsfiddle.net/QeM6g/6/
The jsFiddle example above shows a demo where the above described methods didn't work.
// this is what should work but doesn't
ngModel.$setViewValue(value);
scope.$apply(attr.ngModel,value);
For some reason the scope of the ngModelController is a sibling of my scope. so it doesn't pass the changes back to the parent. at least all other sibling scopes behave as you'd expect. i.e. ng-change works in combination.
Angularjs doesn't deal very well with direct bindings to primitive types.
If you change this line:
$scope.productId = 16;
to something like this:
$scope.selectedProduct = {
id: 16
}
and change those references on the rest of the code, you should be able to overcome the issue.
jsFiddle: http://jsfiddle.net/M2cL7/
Don't bind to primitives in a scope, bind to an object in the scope.
From https://github.com/angular/angular.js/wiki/Understanding-Scopes
... until you try 2-way data binding
(i.e., form elements, ng-model) to a primitive (e.g., number, string,
boolean) defined on the parent scope from inside the child scope. It
doesn't work the way most people expect it should work. What happens
is that the child scope gets its own property that hides/shadows the
parent property of the same name. This is not something AngularJS is
doing – this is how JavaScript prototypal inheritance works. New
AngularJS developers often do not realize that ng-repeat, ng-switch,
ng-view and ng-include all create new child scopes, so the problem
often shows up when these directives are involved.
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models
so
<input ng-model="tweetText">
becomes
<input ng-model="tweet.text">
A great summary is here:
https://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m

Resources