Select2 AngularJS selected item is lost when change state - angularjs

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

Related

Set a $watch on a <SELECT> element in an Angular directive?

How do I set a $watch on the selectedvalue property of a SELECT element in an angular directive? What is the syntax? I have a partial directive below. It doesn't blow up, but the watch never fires.
link: function (scope, elem, attr, ngModel) {
scope.$watch(elem.context.selectedOptions, function (selectedType) {
console.log("nodup choice");
})
}
I don't see a need here to put a watcher over an select dropdown to fire something on its value change. I'd suggest you to use ng-change event over that select box & have function over it. So that will get fire whenever input value gets changed.
<select ng-model="selectedValue" ng-change="changeFn()">
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
</select>

AngularJS Dropdown Directive Set Selected Item Through Two Way Binding

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>

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

scope.$watch in angular directive does not work proprely

I'm using Angular and Bootstrap.
I'm trying to replicate the functionality of ng-model for bootstrap checkbox. What i would like to accomplish is:
i would like that when i click on the checkbox (label really) the model change, and actually that works... but what does not work that when i try to watch the object for changes the behavior is weired, because i need two click insted of one for disable or enable the checkbox.
Moreover if inside the the label element that has as attribute cm-checkbox="model.prop" i put a {{model.anotherprop}} wont work (does not render anything).
From the documentation i understood that because i want the two-way data bind the scope must be defined as i did.
Thank you for your help!
I have the following HTML:
<label id="claim_free" class="checkbox" for="checkbox1" cm-checkbox="model.prop">
<span class="icons"><span class="first-icon fui-checkbox-unchecked"></span><span class="second-icon fui-checkbox-checked"></span></span><input name="claim_free" type="checkbox" value="" data-toggle="checkbox">
same lable text
</label>
And the following JS:
directive('cmCheckbox', function() {
return {
restrict: 'A',
scope: {'cmCheckbox':'='},
link: function(scope,elm,attrs) {
scope.$watch('cmCheckbox', function() {
console.log("first value for "+attrs.cmCheckbox+" is: "+scope.cmCheckbox);
if (!scope.cmCheckbox) {
console.log("checked");
$(elm).removeClass("checked");
$(elm).children("input").removeAttr("checked");
} else { // false and undefined
console.log("unchecked");
$(elm).addClass("checked");
$(elm).children("input").attr("checked","checked");
}
});
$(elm).on('click', function(e) {
e.preventDefault();
var currentValue = elm.hasClass("checked") ? false : true;
scope.$apply(function() {
scope.cmCheckbox = currentValue;
},true);
scope.$parent.$apply();
});
}
};
}).
Here is the jsFiddle: jsfiddle.net/pmcalabrese/66pCA/2

Angularjs ng-repeat race condition in setting dropdown value

I had the problem of getting resource data from an API, loading that into a dropdown select, and setting the selected value of the dropdown. Basically it was trying to set the value of the dropdown before it was populated. I have two different ways to do this, but was wondering if anyone had a "better" way, or a "better practice" way. Here are my two ways.
Option 1: Directive attached to ng-repeat element
Controller
$scope.users = User.query();
$scope.dude={
name: "Dude",
id: 3
}
HTML
<select id="userSelect" ng-show="users.length">
<option ng-repeat="user in users" choose dude="dude">{{user.username}}</option>
</select>
Directive
.directive('choose', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
if (scope.user) {
if (scope.user.id === scope.dude.id) {
$("#userSelect").val(element.val());
}
}
}
}
});
Option 2: Watch for the users length to change (the call is returned, and the dropdown is populated)
Controller
$scope.users = User.query();
$scope.dude={
name: "Dude",
id: 3
}
$scope.$watch('users.length', function() {
$("#userSelect").val($scope.dude.id);
});
HTML
<select id="userSelect" ng-show="users.length">
<option ng-repeat="user in users" value="{{user.id}}>{{user.username}}</option>
</select>
Any opinions on which one is better practice? Or if there is any other better way?
So, promises are your friend for this sort of thing. I'm going to use $http instead of resources, because I'm more familiar with it, but I'm pretty sure recent version of
resources return promises (or can).
Also.. no jquery in your controllers. Use directives like ng-model to change input values.
Also using ng-options to populate the options for a select is more powerful than using ng-repeat on an "option" element.
Here's what a lot of my code looks like (except that I'm using jsonp here instead of just get). http://jsfiddle.net/HB7LU/142/
CONTROLLER:
function MyCtrl($scope, $http) {
// The id we want to select when the data loads:
var desiredId = 3;
// Overly complicated $http request so that I can load with jsonp:
// You could normally use just $http.get()
$scope.users = $http.jsonp('http://www.json-generator.com/j/geku?callback=JSON_CALLBACK').then(function(d) { return d.data.result; });
// Use the returned promise to set the selection when the data loads:
// I'm using the "underscore" library function "findWhere" to set my
// model to the object I want selected:
$scope.users.then(function(d) {
$scope.uservalue = _.findWhere(d,{id:desiredId});
});
}
HTML:
<div ng-controller="MyCtrl">
{{uservalue | json}}
<select ng-model="uservalue" ng-show="users.length" ng-options="user.name for user in users">
</select>
</div>

Resources