AngularJS - directive components dropdown not getting initialized for edit - angularjs

I am new to Angular JS.
What I am trying to do is create a reusable component using Directive. As of now it just has one drop-down. While doing Add the drop-down is getting populated with the values in permissionValues array and binding with the empty model with the selected value is happening. But when I try to Edit the drop-down is not getting initialized with existing model value value.
The directive code.
directive('userPermissions', function() {
return {
restrict: 'E',
template:'<div><select ng-model="ngModel" ng-options="abc [optValue] as abc [optDescription] for abc in array"></select>{{tab}}</div>',
replace: true,
transclude: true,
scope:{ ngModel: '=', tab:'='},
link: function (scope, element, attrs) {
scope.optValue = attrs.optValue;
scope.optDescription = attrs.optDescription;
scope.$watch(attrs.array, function(newVal, oldVal){
if(newVal != oldVal){
scope.array = newVal;
}
});
}
};
});
The HTML code
<user-permissions tab="tab"
ng-model="newUser.canCheckout1"
array="permissionValues"
opt-value="value"
opt-description="label"></user-permissions>
in main controller
$scope.permissionValues = [{label:'Standard', value:'true'},{label:'Restricted', value:'false'}];
As you can see I have add "tab" for testing. I am changing the value of tab on load function which is getting called when I click on Edit. New value of tab is getting printed but the drop-down is not getting initialized.

You're trying to two-way bind your array attribute from the controller using:
scope: {array: '=' ... }
The problem is that you don't have any $scope.array in your controller. So you need to somehow link the two. try this:
link: function(scope, element, attrs){
scope.optValue = attrs.optValue;
scope.optDescription = attrs.optDescription;
scope.array = scope.$eval(attrs.array);
}
This will work but wouldn't create dynamic binding. It will only read the value once and then it won't be updated if the scope[attrs.array] ever changes. In order to do that, you'd have to do a $watch to listen for changes:
scope.$watch(attrs.array, function(newVal, oldVal){
if(newVal != oldVal){
scope.array = newVal;
}
});
Hope that helps.
UPDATE:
To clarify, there are multiple ways to do this, so its up to you for how you want to do it. But the idea behind the scope: { ... } binding in a directive is that you're specifying which properties of the scope you want to be bound and how you want them bound (one-way or two-way). You were telling angular to bind the $scope.array property from your controller into your directive. But because there wasn't any $scope.array property, nothing was being bound. So you can either change your binding to say scope: {permissionValues: '='} or you can change your $scope.permissionValues in your controller function to be $scope.array. Either should work.
But since you're trying to use the array="permissionValues" attribute on your directive to signify which $scope property to use, I figured you would want to do it the way I specified above. Using scope.$watch means you don't have to use any scope: { ... } binding at all. The $watch function manually creates that binding for you and allows you to use the attrs.array value to dynamically bind to the proper scope parameter at runtime. Using scope: {} requires that you know what the scope property is called when the directive is created, and not when it is read from the DOM.
Does that make more sense?

Related

Custom directive doesn't get updated inside cellTemplate of ui-grid

I am using a custom directive named flag inside the cellTemplate of ui-grid. This directive just accepts a country name (USA or CANADA), and displays its corresponding flag. The directive works fine when the page loads for the first time. However, later even if I change the value of flag to anything, the custom directive doesn't update and display the new flag. Not sure what am I missing.
Plnkr
The directive gets compiled once! You need to watch the attribute or set a two way binding!
app.directive('flag', function() {
var flags = {
USA: 'https://github.com/hjnilsson/country-flags/raw/master/png100px/us.png',
CANADA: 'https://github.com/hjnilsson/country-flags/raw/master/png100px/ca.png'
};
return {
restrict: "A",
scope: {
flag: "#"
},
link: function(scope, elem, attrs) {
scope.$watch('flag', function(newValue, oldValue){
angular.element(elem).attr('src', flags[newValue]);
});
}
};
});
working plunkr ==> http://plnkr.co/edit/dcHDMEY8MpQnCnm99aDd?p=preview

Angular directive 1 way binding is working but 2 way binding is not

I am not using scope in my controller as i use controller as etc..
I have proof of concept that plunker without scope is working
1 way binding is working
2 way binding is NOT working - shows literal value
HTML page WORKING
Here: {{detail.program.ldcCode}} SHOWS "Here: PNLC"
<lcd-code code="{{detail.program.ldcCode}}"></lcd-code>
Above passes in 1 way binding of that object/value of PNLC to Directive !
Directive :
return {
replace: true,
restrict: "EA",
scope: {
code: "#"
},
link: function (scope, element, attrs) {
console.log('ldcCode', attrs.code); // PRINTS out PNLC
Thus the above 1 way binding works with passing in {{detail.program.ldcCode}} as an expression , and then in directive the code: "#" along with console.log of console.log('ldcCode', attrs.code); // PRINTS out PNLC
So HERE IS THE PROBLEM, when i switch to my much needed two way data binding
Next is the issue:
Pass from HTML to directive WITHOUT Expression
<lcd-code code="detail.program.ldcCode"></lcd-code>
Directive
scope: {
code : "="
},
link: function (scope, element, attrs) {
console.log('ldcCode', attrs.code);
LITERALLY this prints to chrome dev console as below in bold
ldcCode detail.program.ldcCode
What is going on?
It seems that the attr link function parameter is showing the raw value given to the attribute.
Angular, when using the isolated scope and the two way biding '=' operator, take that value and interpolate it on the parent scope to get the actual value that you can access via the scope parameter on the link function.
Reference to the $compile.directive.Attributes in Angular docs:
A shared object between directive compile / linking functions which
contains normalized DOM element attributes. The values reflect current
binding state {{ }}
If you want to get the interpolate value of the attribute, even not on the isolated scope, you can use the $observe method on it:
function linkingFn(scope, elm, attrs, ctrl) {
// get the attribute value
console.log(attrs.ngModel);
// change the attribute
attrs.$set('ngModel', 'new value');
// observe changes to interpolated attribute
attrs.$observe('ngModel', function(value) {
console.log('ngModel has changed value to ' + value);
});
}

ngModel and How it is Used

I am just getting started with angular and ran into the directive below. I read a few tutorials already and am reading some now, but I really don't understand what "require: ngModel" does, mainly because I have no idea what ngModel does overall. Now, if I am not insane, it's the same directive that provides two way binding (the whole $scope.blah = "blah blah" inside ctrl, and then {{blah}} to show 'blah blah' inside an html element controlled by directive.
That doesn't help me here. Furthermore, I don't understand what "model: '#ngModel' does. #ngModel implies a variable on the parents scope, but ngModel isn't a variable there.
tl;dr:
What does "require: ngModel" do?
What does "model : '#ngModel'" do?
*auth is a service that passes profile's dateFormat property (irrelevant to q)
Thanks in advance for any help.
angular.module('app').directive('directiveDate', function($filter, auth) {
return {
require: 'ngModel',
scope: {
model : '#ngModel',
search: '=?search'
},
restrict: 'E',
replace: true,
template: '<span>{{ search }}</span>',
link: function($scope) {
$scope.set = function () {
$scope.text = $filter('date')($scope.model, auth.profile.dateFormat );
$scope.search = $scope.text;
};
$scope.$watch( function(){ return $scope.model; }, function () {
$scope.set();
}, true );
//update if locale changes
$scope.$on('$localeChangeSuccess', function () {
$scope.set();
});
}
};
});
ngModel is an Angular directive responsible for data-binding. Through its controller, ngModelController, it's possible to create directives that render and/or update the model.
Take a look at the following code. It's a very simple numeric up and down control. Its job is to render the model and update it when the user clicks on the + and - buttons.
app.directive('numberInput', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<span></span><button>+</button><button>-</button>',
link: function(scope, element, attrs, ngModelCtrl) {
var span = element.find('span'),
plusButton = element.find('button').eq(0),
minusButton = element.find('button').eq(1);
ngModelCtrl.$render = function(value) {
updateValue();
};
plusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue + 1);
updateValue();
});
minusButton.on('click', function() {
ngModelCtrl.$setViewValue(ngModelCtrl.$modelValue - 1);
updateValue();
});
function updateValue(value) {
span.html(ngModelCtrl.$modelValue);
}
}
};
});
Working Plunker
Since it interacts with the model, we can use ngModelController. To do that, we use the require option to tell Angular we want it to inject that controller into the link function as its fourth argument. Now, ngModelController has a vast API and I won't get into much detail here. All we need for this example are two methods, $render and $setViewValue, and one property, $modelValue.
$render and $setViewValue are two ways of the same road. $render is called by Angular every time the model changes elsewhere so the directive can (re)render it, and $setViewValue should be called by the directive every time the user does something that should change the model's value. And $modelValue is the current value of the model. The rest of the code is pretty much self-explanatory.
Finally, ngModelController has an arguably shortcoming: it doesn't work well with "reference" types (arrays, objects, etc). So if you have a directive that binds to, say, an array, and that array later changes (for instance, an item is added), Angular won't call $render and the directive won't know it should update the model representation. The same is true if your directive adds/removes an item to/from the array and call $setViewValue: Angular won't update the model because it'll think nothing has changed (although the array's content has changed, its reference remains the same).
This should get you started. I suggest that you read the ngModelController documentation and the official guide on directives so you can understand better how this all works.
P.S: The directive you have posted above isn't using ngModelController at all, so the require: 'ngModel' line is useless. It's simply accessing the ng-model attribute to get its value.

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

Angular custom directive with filter in attribute

I would like make angular directive where I use filter on data which are passed as argument.
So something like this:
<div class="my-module" data="a in array | orFilter:filter"></div>
Where "data" is attribute of directive "my-module".
I looked into ngRepeat source, but they parse ng-repeat argument and then they evaluate them. I can't use ng-repeat because I'm creating new instance of object (Marker for map) from data parameter.
Is it realy so hard? Is possible do this in custom directive and how?
Small example what i want: http://jsfiddle.net/PjRAr/1/
EDIT
I'm trying extend this map wrapper to render filtered markers.
My potencional solution is holding copy of all markers and visible markers. Add $watch to filter and when filter was changed call $scope.markers = $scope.$eval("allMarkers | orFilter:filter");.
With this solution we need hold two copy of all markers (~500).
You can $eval the filter expression.
in your directive link function:
elem.text( scope.$eval( attrs.data ).join(', ') );
in your template:
<div my-directive data="['Hello', 'xxx', 'World'] | filter:'o'"></div>
and the directive renders (by filtering-out 'xxx') to:
Hello, World
EDIT:
If the values are dynamic, you can of course do:
scope.$watch( attrs.data, function( arr ) {
elem.text( arr.join(', ') );
});
I do not think you can avoid having $watch, though.
I think you're mixing a few things. I'm not sure why you don't want to use ng-repeat, that's what it's made for. Because you isolate the scope you don't have access to the parent scope. The '=' binding tries to bind your isolated scope data attribute to the parent scope's model called what is in the attribute, but you can't bind to something that has been filtered. If you don't want to repeat the div with the attributes, put them on your own element, it just creates the content...
Here's a fiddle showing both using ng-repeat. You can see the binding is 2-way, it adds an updated: true property.
(FIDDLE)
link: function (scope, element, attrs, ctrl) {
element.append('<p>a: ' + scope.data.a + ', b: ' + scope.data.b);
scope.data.updated = true;
}
So, when I use one-way binding i got empty array in $watch (i think it's trying evaluate in local scope [removed "data" from directive scope for one way binding]. When i use two way binding [in directive scope is {data: "=data"}] i got error "Watchers fired in the last 5 iterations" (it's common error for filtering in angular).
So my solution:
Directive:
...
scope: {
data: "=data"
}
...
link: function (scope, element, attrs, ctrl) {
...
scope.$watch("data", function (newValue) {
angular.forEach(newValue, function (v, i) {
model.add(v);
}
}
}
...
Controller:
...
$scope.filter = { a:true, b:false, ... };
$scope.all = [..data..];
$scope.visible = [..data..];
$scope.$watch("filter", function(newValue) {
$scope.visible = $scope.$eval("all | orFilter:filter");
}, true);
...
HTML:
<div my-module data="visible"></div>
Thank you very much guys, you're help me so much. I'm learned many new things about angular binding.

Resources