Two way binding from the link function - angularjs

Can someone tell me why I am not able to two way bind from the link function?
Please refer to this plunk: http://plnkr.co/edit/RI1ztP?p=preview
The below watch successfully adds the collection to attrs.ngModel but I dont see it reflecting in the parent controller
scope.$watchCollection("selectedItems",function(collection){
attrs.ngModel = [];
for(var i=0;i<collection.length;i++){
attrs.ngModel.push(collection[i]);
}
console.log("ngModel",attrs.ngModel);
});
Cant see the collection over here (selectedUsers):
<body ng-controller="mainCtrl">
<div multi-select-search-box ng-model="selectedUsers" label="name" my-options="state in states"></div>
{{selectedUsers}}
If you look at the above html, I am binding the selectedUsers array to ng-model. In my link function, i add the selected users to attrs.ngModel array. When I look at the console, the selectedUsers are added to attrs.ngModel but the array isn't reflected back on the html {{selectedUsers}}

The data bound to the ng-model of your multi-select-search-box is $scope.selectedUsers.
Therefore to register a change in the DOM you have to update that variable rather than ng-model.
scope.$watchCollection("selectedItems",function(collection){
for(var i=0;i<collection.length;i++){
scope.myNgModelVar.push(collection[i]);
}
});
Since ng-model is a string that gets $parse()/$eval() called on it to evaluate it as an expression, updating that ng-model value won't do you any good.
EDIT:
After some clarification it appears that this is a custom directive designed to be reusable. So therefore we do not want to stick variables from your controller inside the directive. Instead, you should bind a directive attribute to your directives scope.
// Directive Def. Object:
return {
restrict: "AE",
scope: {
myNgModelVar: "=",
bindModel: "=ngModel" //This is the alternate method aliasing ngModel var with a scope var.
},
template: "<input ng-model='myNgModelVar' />"
};
Although you could use ngModel by using an alias scope: {bindModel:'=ngModel'}, this gives you an isolated scope variable that you bind to ngModel instead. Therefore keeping your directive reusable.

The solution was to require the ng-model controller and sync changes using the viewValue array:
scope.$watchCollection("selectedItems",function(collection){
ctrl.$viewValue.splice(0,ctrl.$viewValue.length);
for(var i=0;i<collection.length;i++){
ctrl.$viewValue.push(collection[i]);
}
});
and
require: 'ngModel'

Related

angular directive isolated scope property updated in link function but not reflecting on directive view

I have a case where, I have a directive which get a value form Controller $scope.colorName. Directive bind it one-way and keep this value in isolated scope "colorVar". Template of directive render "colorVar" as {{ colorVar }} .
I need to change the value of "colorVar" in link function of directive. but its not reflecting on UI.
HTML:
<div ng-app="myApp" ng-controller="appCtrl">
{{colorName}}
<my-directive color='{{colorName}}'>
</my-directive>
</div>
JavaScript:
angular.module('myApp', []).controller('appCtrl',function($scope){
$scope.colorName='red';
})
.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
colorVar: '#color'
},
template: '<span> {{ colorVar }} </span><span>{{extra}}</span>',
link:function(scope,element,attrs){
scope.colorVar='orange';
scope.extra='kk';
}
};
});
In Link function I have updated scope.colorVar with 'orange' but is don't reflect on UI, but scope.exta do reflect on UI.
http://jsfiddle.net/ut7628d7/1/
Any idea what am I doing wrong. and why this is happening and how to achieve it ?
Thanks.
It depends on whether you want the directive's updated value to also change the original value in the controller or not:
Modify the original variable
If you want the directive to modify the original colorName in the controller, pass colorName by reference and use a two-way binding in the template:
<my-directive color='colorName'> <!-- instead of color='{{colorName}}' -->
and in the directive:
scope: {
colorVar: '=color' // instead of '#color'
},
http://jsfiddle.net/9szjpx9d/
The output from this will be "orange orange kk" because it's the same object throughout, so when the directive changes it to 'orange' it will affect both places.
Copy the variable
If you want the directive to only modify its own value, and output "red orange kk", then keep an attribute binding as you have now, but delay the directive link function by one tick using $timeout, so that the value it sets on scope will overwrite the value received via the directive attribute:
$timeout(function() {
scope.colorVar = 'orange';
scope.extra = 'kk';
});
Meanwhile the separate original color value will remain untouched in the controller, because it was passed to the directive as a string rather than as an object reference.
http://jsfiddle.net/mpb2cuaj/
If you want to be able to change the color form inside your directive then use a two way binding. e.g.
scope: {
colorVar: '=color'
}
HTML
<my-directive color='colorName'></my-directive>
http://jsfiddle.net/0he428hw/

Two-way binding within nested directives and ui-select

Problem I have been working on:
The original problem (which only addressed one-way binding) can be found here:
Unable to pass/update ngModel from controller to directive
I have managed to get the one-way binding from inside the directive out to the view working. But if I want push in a selection when the page loads, for example, I cannot get the two-way binding to function. I can see that the ng-model inside the first directive is getting the data, but I tried various scope settings with the child directive and either it breaks it or it does the same thing as before - nothing.
I have a basic $watch function set up, so that when I push a simple object into the binding that is attached to ng-model in the view, the watcher assigns the $viewValue to the directive's scope object. It does this, and the directive responds only by having any existing selection wiped out, even though I can clearly see the objects inside ng-model binding assigned to ui-select.
Here is the watch function:
scope.$watch(function() {
return ngModel.$viewValue;
}, function(newVal) {
console.log(newVal, scope.options);
scope.options.selected = newVal;
});
I use this function to update the view whenever we interact with the ui-select (which works fine):
scope.changer = function() {
ngModel.$setViewValue(scope.options.selected);
console.log(scope.options.selected);
};
A Plunker for tinkering
So the expected behavior is:
selections from the ui-select should be displayed in the select, and also get passed to the view
by clicking the 'Change' button, data should be displayed in the view, and get passed to the ui-select
The situation was that I had a directive with ui-select inside it. The way I was able to get data from the main controller scope through the directive scope and down to ui-select's scope was by putting a watch function on ngModel.$viewValue in the link function of the directive.
I then assigned that new value, whenever there was a change, to an object on that directive's scope, to hold it for me. Then I set a watch function in the link function of ui-select to watch scope.$parent.myVal so that if anything was ever pushed to that variable from the main controller, ui-select would see it. When that happened, I would assign that new value to $select.selected which is the container for selected items in the ui-select directive.
It should be noted that ui-select uses scope: true in it's directive, and thus becomes a child of whatever scope it is instantiated in, which allows you to access the scope of it's parent with $parent.
Watch function for the directive:
scope.$watch(function() {
return scope.$parent.myVar;
}, function(newVal) {
$select.selected = newVal;
})
Watch function to be added to ui-select:
scope.$watch(function() {
return ngModel.$viewValue;
}, function(newVal) {
scope.myVar = newVal;
})
Plunker Demo

Value of input field is being removed by directive

I have a simple Angular Problem - I think it's probably a case of can't see the wood for the trees here.
I have an input field with a directive attached. The purpose is eventually to compare new with old data and show a popup. However, as soon as I add the directive attribute to the input field, the value disappears:
Plunk here: http://plnkr.co/edit/BQvKGe6kjuD0ThPBYJ4d?p=preview
HTML:
First Name:
<input type='text' ng-model='currentEditItem.strFirstName' name='strFirstName' id='strFirstName'
cm-popover="currentEditItem.personOldData.strFirstName"/>
<br><br>
ngModel: {{currentEditItem.strFirstName}} <br>
cmPopover: {{currentEditItem.personOldData.strFirstName}}
JS
var app = angular.module('app', []);
app.controller('Ctrl', function ($scope) {
$scope.currentEditItem = {};
$scope.currentEditItem.strFirstName = "Bob";
$scope.currentEditItem.personOldData = {};
$scope.currentEditItem.personOldData.strFirstName = "Roger";
});
app.directive("cmPopover", function () {
return {
scope: {
ngModel: "=",
cmPopover: "="
},
link: function (scope, elem, attrs) {
console.log("ngModel", scope.ngModel);
console.log("cmPopover", scope.cmPopover);
}
}
});
If you go to the Plunk and remove the cm-popover attribute, the input field is filled with the value from the model. When the attribute is added the value disappears although the model is still in the scope with the correct value.
In your directive you declare an isolate scope. This input's scope is now this isolate scope since it's the directive element. It's looking for the currentEditItem object which doesn't exist in the isolate scope
ngModel does not create a new isolated scope for itself so it can $watch without having to hardcode a $parent in it's internal code.
But then you add another directive on the same DOM node that creates an isolated scope for itself. Couple this with the fact that you can only have a single isolated scope on a DOM node and you basically force ngModel to use/work with the same scope cmPopover created.
So when writing ng-model="currentEditItem.strFirstName" you are actually addressing the $scope inside the cmPopover directive, no the one in the (parent) controller. You can check this is the case by using ng-model="$parent.currentEditItem.strFirstName" - and it will work.
There's quite a lengthy conversation here with a lot of possible workarounds and solutions that leads to an actual fix in release 1.2.0.
So long story short: update to at least AngularJS 1.2.0 and this will work.

Angular JS directives as native inputs

How can I do this thing:
HTML:
{{bar}}
<input type="text" ng-model="bar">
<foo ng-model="bar">
JS:
app.directive('foo', function(){
return {
restrict: 'E',
template: '<textarea>{{value}}</textarea>',
scope: { value : '=' }
controller: function($scope, $element){
}
}
});
I want change "bar" value when foo directive changes and change value in foo directive when text input changes.
How can I do that?
text input changes: textarea changes.
textarea changes: text input changes too.
but textarea should be as directive with restrict: "E"
So, your problem is that your isolate scope expect an attribute called value.
value: "=" means that you expect an attribute with tha name of value.
value: "=ngModel" means that you will have a property called value in your isolated scope linked to the ng-model attribute.
I give you here 2 examples:
http://jsbin.com/EyareCo/1/edit
The first one, just bind your bar with the internal scope via a value attribute. Works just fine.
The second one, uses ngModelController which is more advanced that the first one. The idea behind using ngModelController is to provide validation and more stuff.
You can just simply use scope.bar in all three cases
It would look like
{{bar}}
<input type="text" ng-model="bar">
<foo value="bar">
You shouldn't use ng-model in your directive, because you deal with the isolated scope hear, which contains only 'value' field.
The '=' binding that you've provided will do the rest of the job for you. When value changes through the ng-model from your input it will also change inside your directive
UPDATE:
To bind textarea with 'bar' value you should also use ng-model hear, but instead of 'bar' we should use 'value' hear, because as I write before, there is no scope.bar field here, but only scope.value field, but it directly binded with 'bar' property of parent scope from outside. So template will looks like : <textarea ng-model='value'></textarea>.
If you want to update value when some event happens (not-angular event), you should use $scope.$apply to notify that values have been changed.
Check this fiddle:
http://jsfiddle.net/TNySn/1/
Hear, when we click button, all 3 values changing because changeValue function wrapped in $scope.$apply function, and when we change textarea text, text-box and {{bar}} output are also change

AngularJS Directive - Template Select Not Updating Ng-Model

I have two directives in my module. The first is from https://github.com/banafederico/angularjs-country-select. I used it as a model for the second. The two directives represent input fields, but the second one is not storing a value. The link function is not updating the ng-model value.
link: function(scope, elem, attrs) {
if (!!attrs.ngModel) {
var assignDivision = $parse(attrs.ngModel).assign;
elem.bind('change', function(e) {
assignDivision(elem.val());
});
scope.$watch(attrs.ngModel, function(division) {
elem.val(division);
});
}
}
In my ide (netbeans) the 2nd (dependent) drop-down does not display the selected value in the drop-down or update the model. In this fiddle (http://jsfiddle.net/676mp/), the display updates, but the model does not. I am unsure how to update the value of the model to the selected value.
Its because in the second directive you defined scope: {...} when passing in the country value. This creates an isolate scope in the directive and thus ng-model on the element doesn't really work, you need to create a two way binding to the parent variable in the directive's new isolate scope
http://jsfiddle.net/676mp/2/
scope: {
country: '#',
myDivision: '=division'
}
And then in the HTML
<division-select division="myDivision" country="{{myCountry}}"></division-select>
EDIT: Also note that in the fiddle I changed your template for the directive to include its own ng-model for its own scope

Resources