AngularJS Dropdown Directive Set Selected Item Through Two Way Binding - angularjs

I have an angularjs dropdown directive. I want to be able to pass the id of the item I want to be selected as an attribute of the directive. Something like this:
<dropdown selected-item-id="ctrl.selectedItemId"></dropdown>
I implemented this and it's not working. If I display the value of itemId on the directive code, I can see the right value, but the dropdown selection does not update. Here's the relevant code:
(function () {
'use strict';
var dropdown = function ($state, service) {
return {
restrict: 'E',
replace: true,
templateUrl: '/dropdown.html',
scope: {
selectedItemId:"="
},
link: function (scope, elem, attr) {
service
.getItems()
.then(function (items) {
scope.items = items;
});
}
};
};
dropdown.$inject = ['$state', 'service'];
angular.module('app').directive('dropdown', dropdown);
})();
<select class="form-control"
ng-model="selectedItemId"
ng-options="item.id as item.name for item in items">
<option value="" selected disabled>Select an item</option>
</select>
Like I said, if I display the selectedItemId on the directive template (e.g. as one of the options) I see the right id value, however the dropdown selection doesn't change.
Any thoughts?
EDIT:
I had a typo (happened when typing the code, the actual code on my editor is correct) on the dropdown's property, item-id to selected-item-id

You are not binding selected value to item-id as you think according to your html code. You are binding selected value to selected-item-id.
Try changing your html to this:
<dropdown selected-item-id="ctrl.selectedItemId"></dropdown>

Looks like you might be having a race condition on the $digest cycle. If you call $apply() from your service callback on the first version of your code it should work. However, you will have the side effect that from time to time angular will complain about an $apply being already in progress so the second version of your code should do the trick.

I'm not sure why it wasn't working like I had it, but I made some changes an now it works ok. Here's what I did (the explanation is included as comments on the source code):
//Here I use the selectedItem bidirectional binding on the .then of my service call
//to get the id of the items that's supposed to be selected.
//Then, I filter the array of items using that id, so I get the actual object that matches the id.
//Finally, I set the scope object "selectedItem" to that object I just got.
//The "selectedItem" object is what's bound to the select via ng-model so that does the selection.
(function () {
'use strict';
var dropdown = function ($state, service) {
return {
restrict: 'E',
replace: true,
templateUrl: '/dropdown.html',
scope: {
selectedItemId:"="
},
link: function (scope, elem, attr) {
service
.getItems()
.then(function (items) {
scope.items = items;
var selectedItem = $filter('filter')(items, { id: scope.selectedItemId })[0];
scope.selectedItem = selectedItem;
});
}
};
};
dropdown.$inject = ['$state', 'service'];
angular.module('app').directive('dropdown', dropdown);
})();
<!--Here I replaced the ng-model to use an object rather than an id and did the same on the ng-options -->
<select class="form-control"
ng-model="selectedItem"
ng-options="item as item.name for item in items">
<option value="" selected disabled>Select an item</option>
</select>

Related

Custom Directive : Dropdown selected value not binding

I'm trying to create a custom directive for a drop down control in AngularJS 1.4.4. I can handle the selected event, but i can not get the binding for what is selected in the drop down list.
I want to call this from Html markup the following way.
<my-dropdown-list source="myList" destination="mySelection" />
The angular js custom directive is here.
(function() {
var directive = function($compile) {
return {
restrict: 'E',
scope: {
model: '=source',
selectedValues: '=destination'
},
controller: function($scope) {
$scope.onSelChange = function() {
alert('called');
console.log($scope.selectedItem.Code, $scope.selectedItem.Name);
};
// $scope.selectedItem is always undefined here.
},
link: function ($scope, $elem) {
var rowHtml =
'<select ng-options="item as item.Name for item in model" ng-model="selectedItem" ng-change="onSelChange()"></select>';
$elem.html(rowHtml);
$compile($elem.contents())($scope.$new());
}
};
};
my.directive('myDropdownList', directive);
})();
I'm new to Angular, so this may be something small that i missed here, but i can't seem to get a value for 'selectedItem'
I find out this in AgularJS document
Note that the value of a select directive used without ngOptions is
always a string. When the model needs to be bound to a non-string
value, you must either explictly convert it using a directive (see
example below) or use ngOptions to specify the set of options. This is
because an option element can only be bound to string values at
present.
Link: https://docs.angularjs.org/api/ng/directive/select
You should use ngRepeat to generate the list like this post:
Angularjs: select not updating when ng-model is updated

Angular binding does not work in data- attribute

I am using some css html template that comes with many html components and with lots of data-attributes for various things. For example for slider it has something like
<div class="slider slider-default">
<input type="text" data-slider class="slider-span" value="" data-slider-orientation="vertical" data-slider-min="0" data-slider-max="200" data-slider-value="{{ slider }}" data-slider-selection="after" data-slider-tooltip="hide">
</div>
Here I am trying to bind the value
data-slider-value="{{ slider }}"
But it's not working. Variable 'slider' is set in the $scope as:
$scope.slider = 80;
Same value 80 shows up right when I bind it as:
<h4>I have {{ slider }} cats</h4>
I have also tried
ng-attr-data-slider-value="{{ slider }}"
It didn't work.
Update
The directive has something like this
function slider() {
return {
restrict: 'A',
link: function (scope, element) {
element.slider();
}
}
};
where element.slider(); calls the code in bootstrap-slider.js (from here) for each of the sliders.
I played with this for a while, and came up with a few options for you. See my Plunkr to see them in action.
Option 1: No need to update the scope value when the slider changes
This will work with the HTML from your question. The following is what you should change the directive code to.
app.directive('slider', function slider() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
attrs.$observe('sliderValue', function(newVal, oldVal) {
element.slider('setValue', newVal);
});
}
}
});
Option 2: Two way binding to the scope property
If you need the scope property to be updated when the slider handle is dragged, you should change the directive to the following instead:
app.directive('sliderBind', ['$parse',
function slider($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var val = $parse(attrs.sliderBind);
scope.$watch(val, function(newVal, oldVal) {
element.slider('setValue', newVal);
});
// when the slider is changed, update the scope
// property.
// Note that this will only update it when you stop dragging.
// If you need it to happen whilst the user is dragging the
// handle, change it to "slide" instead of "slideStop"
// (this is not as efficient so I left it up to you)
element.on('slideStop', function(event) {
// if expression is assignable
if (val.assign) {
val.assign(scope, event.value);
scope.$digest();
}
});
}
}
}
]);
The markup for this changes slightly to:
<div class="slider slider-default">
<input type="text" data-slider-bind="slider2" class="slider-span" value="" data-slider-orientation="vertical" data-slider-min="0" data-slider-max="200" data-slider-selection="after" data-slider-tooltip="hide" />
</div>
Note the use of the data-slider-bind attribute to specify the scope property to bind to, and the lack of a data-slider-value attribute.
Hopefully one of these two options is what you were after.
I use .attr and it works for me. Try this:
attr.data-slider-value="{{slider}}"

AngularJS - ngOptions in a select - How to validate before changing the model?

Im using AngularJS. I have a select with several options. To work with this options Im using ngOptions and ng-model. When the value in the select changed, I do a PUT operation. If it successes then this selection should be transmitted to the model. In case of failed, the model should not get this new selection.
<select class="form-control" ng-change="updateSelection(object)"
ng-model="object.status" ng-options="status as label for (label, status) in CONSTANTS">
</select>
My issue is that due to ng-model and two-way binding, whenever I do a change the new selection is transmitted to the model without any validation. Is there any way to do some validation before updating the model?
You should just use one variable for selection (that is assigned to ng-model), and one variable for the final value:
<select ng-change="updateSelection(object, selection)"
ng-model="selection"
ng-options="status as label for (label, status) in CONSTANTS">
</select>
And in the controller:
$scope.updateSelection = function(object, selection){
$http.put(url, selection)
.then(function(data){
object.status = selection;
})
.catch(function(){
selection = null;
}
}
EDIT:
For a more "proper" approach, you could use an async validator. This can be attached to any input with ng-model.
Here's an example of fake validator with $timeout:
app.directive("fakeValidator", function($timeout, $q){
return {
require: "ngModel",
scope: {
fakeValidator: "="
},
link: function(scope, element, attrs, ngModel){
ngModel.$asyncValidators.fake = function(modelValue, viewValue){
// don't invalidate empty values
if (!viewValue) {
return $q.when(true);
}
return $timeout(function(){
// invalidate if the attribute value was false
return scope.fakeValidator ? true : $q.reject();
}, 1000)
}
}
}
});
Invalid values would not propagate to the model:
<select ng-model="object.status" fake-validator="false"
ng-options="status as label for (label, status) in CONSTANTS">
</select>
plunker

Select2 AngularJS selected item is lost when change state

I have a problem with my AngularJS component Select2, I create a directive:
app.directive('autocomplete', function() {
return {
restrict : 'A',
link : function( scope, element, attrs, ngModelCtrl) {
$(function() {
element.select2({
formatNoMatches : function() {
return 'No results';
}
});
});
}
}
});
Implementation:
<select id="animals" name="animals" class="form-control" ng-model="item.animals" autocomplete >
<option ng-repeat="animal in animals" value="{{animal.code}}" >{{animal.value}}</option>
</select>
The component at this point works well, but, I have a simple $state.go that load other view and later return to this main view, when back to the view the selected animal doesn't load but the value of item.animals contain the previous correct value selected.
¿How I can selected again the item? I really don't understand why the selection is lost when change the state.
¡Thanks for advance!
That is why the ng-options exists. You could use the ng-options and the previous selected value will be loaded correctly.
<select id="animals" name="animals" class="form-control"
ng-model="item.animals" autocomplete=""
ng-options="animal.code as animal.value for animal in animals">
</select>
Example Plunker: http://plnkr.co/edit/5XLjJ2gm9ksjQfmTAPPp?p=preview

unable to make custom html with angular tags work with select2

I am using angular-ui's ui-select2. I want to add custom html formatting to the selections. Select2 allows this by specifying the formatSelection in its config.
I have html with angular tags as below that I want to use for formatting the selection-
var format_code = $compile('<div ng-click="showHide=!showHide" class="help-inline"><div style="cursor: pointer;" ng-show="!!showHide" ng-model="workflow.select" class="label">ANY</div><div style="cursor: pointer;" ng-hide="!!showHide" ng-model="workflow.select" class="label">ALL</div></div>')( $scope );
var format_html = "<span>" + data.n + ' : ' + data.v +' ng-bind-html-unsafe=format_code'+ "</span>"
$scope.select_config = {
formatSelection: format_html
}
If I compile the html as in above and assign it, I just see an [object,object] rendered in the browser. If I dont compile it, I see the html rendered properly, but the angular bindings dont happen, ie the clicks dont work.
Any ideas what is wrong?
I had the same problem, select2 loading in a jquery dialog and not using the options object I would give it.
What I ended up doing is isolating the element in a directive as following:
define(['./module'], function (module) {
return module.directive('dialogDirective', [function () {
return {
restrict: 'A',
controller: function ($scope) {
console.log('controller gets executed first');
$scope.select2Options = {
allowClear: true,
formatResult: function () { return 'blah' },
formatSelection: function () { return 'my selection' },
};
},
link: function (scope, element, attrs) {
console.log('link');
scope.someStuff = Session.someStuff();
element.bind('dialogopen', function (event) {
scope.select2content = MyResource.query();
});
},
}
}]);
and the markup
<div dialog-directive>
{{select2Options}}
<select ui-select2="select2Options" style="width: 350px;">
<option></option>
<option ng-repeat="item in select2content">{{item.name}}</option>
</select>
{{select2content | json}}
</div>
What is important here:
'controller' function gets executed before html is rendered. That means when the select2 directive gets executed, it will already have the select2Options object initialized.
'link' function populates the select2content variable asynchronously using the MyResource $resource.
Go on and try it, you should see all elements in the dropdown as "blah" and selected element as "my selection".
hope this helps, that was my first post to SO ever.

Resources