Place 'selected' attribute in matching option (in directive) - angularjs

How do I go about placing the 'selected' attribute in the option where device.types value is equivalent to. For some weird reason, the first option gets displayed even though device.type is of some other value. Where's the best place to put the code in the directive, link, compile, or controller?
template.html
<div>
<select ng-model="device.type">
<option value="value1">value1</option>
<option value="value2" selected>value2</option>
.
.
.
<option value="valueN">valueN</option>
</select>
</div>
directive
app.directive('details', function() {
return {
restrict : 'E',
scope : {
device: '=',
...
},
templateUrl : 'template.html',
link : function(scope, element, attrs) {
// put 'selected' on option element where device.type is equivalent to
// is it preferrable to put logic here? or in controller or compile
}
};
});
Appreciate your inputs. Thanks.

You don't need to create a directive to achieve that.
Just fill you select with ng-options and use track by <some field>.
Then, when your select's model changes, it will select the right option corresponding to the field you choose in track by <some field>.
Something like that :
<select
ng-model="device"
ng-options="device as device.type for device in devices track by device.type">
</select>
So, when you put a device object in the select's model, it will select the option with the device.type matching your model.

Related

Directive isn't loading scope variable

So I have a very simple directive that's supposed to execute a jquery plugin function:
angular.module('myproject.directives').directive('starRating', function () {
return {
restrict: 'A',
scope: {
rating: '='
},
link: function (scope, elem, attr) {
console.log('rating', scope.rating);
elem.barrating({
theme: 'css-stars',
readonly: true
});
elem.barrating('set', scope.rating);
}
};
});
Here is the HTML:
<select class="service-rating"
ng-show="!!currentJob.Review.ServiceRating"
star-rating rating="currentJob.Review.ServiceRating">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
The 'currentJob' variable is only set after an $http call, however the div is only set to show once this is populated. The log is returning 'null' for scope.rating, however if I log 'scope' on it's own, it clearly shows a 'rating' property that's populated as expected.
Also if I just enter a hard-coded number for the 'rating' attribute the directive works as expected.
I'm not really sure where I'm going wrong here? Any ideas?
Essentially the problem is, as you're using ng-show, your directive gets loaded into DOM tree before your star value retrieved from the Ajax, it processed and barrating gets attached to DOM with star value 0. Basically what ng-show does is, it just hide or show DOM on html, just by toggling display css property on DOM.
So You can have two options to make your star component working.
Use ng-if instead of ng-show
Use $watch inside a component to update star rating(this will be good solution to go).
link: function (scope, elem, attr) {
console.log('rating', scope.rating);
elem.barrating({
theme: 'css-stars',
readonly: true
});
scope.$watch('star', function(newValue){
elem.barrating('set', newValue);
});
}
You could have combination of both. Show ratings only when you have rating using ng-if & then any change in rating will be taken care by $watch to update on barrating element.
Change the directive to react to changes in the rating:
app.directive('starRating', function () {
return {
restrict: 'A',
scope: false,
link: function (scope, elem, attr) {
elem.barrating({
theme: 'css-stars',
readonly: true
});
scope.$watch(attr.rating, function (newValue) {
elem.barrating('set', newValue);
console.log('rating', newValue);
});
}
};
});
Coded this way, on every digest cycle the watcher checks for changes to the Angular Expression defined by the rating attribute, and updates the barrating plugin appropriately.
Update
I did think of using a watch but I was curious as to why my original setup didn't work.
The original setup didn't work because it sets the star rating, only once, when the directive initializes. Since the data arrives from the server after the directive initializes, the new data is not seen by the plugin. By using a watch, the setting updates every time the controller changes the variable including the time when the value arrives from the server.
Also notice that the other answer hardwires the watch to a specific scope variable (not wise). It is wiser to use an attribute to declare the specific scope variable. It makes for a more versatile directive.
When a directive lacks a template that uses AngularJS bindings, it is wiser to avoid isolate scope. Put watches directly on attributes as shown in this example.

Providing id values for each option in ng-options

I have a select box that looks like this:
<select ng-model="ctrl.arrayVal" ng-options="option for option in ctrl.myarray"></select>
where ctrl.myarray looks something like:
ctrl.myarray = [100, 200, 700, 900];
This all works great, but for automated testing purposes I need to provide an id to each of the dynamically generated options. So for example if I rewrote this using ng-repeat then I would need something like this:
<select ng-model="ctrl.arrayVal">
<option id="array-{{option}}" ng-repeat="option in ctrl.myarray" value="{{option}}">{{option}}</option>
</select>
Notice the array-{{option}} id that is possible in this second snippet using ng-repeat. I need to provide a similar id but using the first ng-options method.
Is there a way to do this?
Currently ng-options only lets you specify the value property of the <option> element using the select as or track by syntax. It is also possible to add the disabled property if your data source is an object (not an array). It is not possible to further customize the fields of the <option> element including id.
Docs on ngOptions
Maybe you can achieve your goal with a custom directive. For example:
angular.module('myApp', [])
.directive('idAdder', function() {
return {
link: function(scope, element, attrs) {
scope.$watch(attrs.ngModel, function() {
scope.$evalAsync(function() {
angular.forEach(element.find('option'), function(o, k) {
o.id = o.value;
});
});
});
}
}
});
Then, use it as a custom attribute:
<select ng-model="ctrl.arrayVal"
ng-options="option for option in ctrl.myarray"
id-adder></select>
The $watch and $evalAsyncare used to execute the function after the options have been populated/created.

Issues with passing ng-model to custom directive and making it work

I've created a custom directive that auto-populates a select with a list of countries. The directive has a preselect isolate scope attribute, that if set to true will preselect a country.
ng-model is also passed as an isolate scope attribute
The problem is that ng-model won't be set.
Could anyone give me some pointers on how to make the <auto-countries> directive so that I'm able to set ng-model ?
here's my directive's code:
app.directive('autoCountries', function() {
return {
restict: 'E',
controller: 'CountriesCtrl as ctrl',
scope: {
preselect: '=',
ngModel: '='
},
template: [
'<select ng-if="!preselect" ng-model="ngModel">',
'<option value="">Select Country</option>',
'<option ng-repeat="country in ctrl.countries" value={{country.name}}>',
'{{country.name}}',
'</option>',
'</select>',
'<select ng-if="preselect" ng-model="ngModel">',
'<option ng-repeat="country in ctrl.countries" ng-selected="ctrl.countryFromIP == country.name" value={{country.name}}>',
'{{country.name}}',
'</option>',
'</select>',
].join('')
}
})
What makes things more weird is that in a simpler version of the directive that doesn't use at all the preselect, the ng-model will be set.
It's kinda hard to understand without an example, so here's a Plunkr!
http://plnkr.co/edit/e1HPgGQlne7Q4TcNJ9XT?p=preview
You should never use ng-repeat on <option>, instead use ng-options on the <select>:
<select ng-model="ngModel" ng-options="country.name as country.name for country in ctrl.countries"></select>
http://plnkr.co/edit/ADTPOIKZnnt14lVcr8TN?p=preview
You should use ngOptions instead of ngRepeat. I forked dave's plnkr and adjusted it a bit. Your template with and without preselection will look like this:
'<select ng-model="ngModel" ng-options="country.name as country.name for country in ctrl.countries"></select>'
I did the pre-selection in the countries controller instead of the view/template, just like in angular's documentation for ngOptions.
$scope.ngModel = $scope.preselect ? $scope.ngModel = vm.countries[3].name : "";
(if preselect equals true then set default for ngModel, else set empty string - javascript ternary operator.)

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

angular js: updating $watcher from directive not working

I have an application where I have a 2 drop down boxes, one for state and another for city, and a directive that has a mock up of values not tied to anything.
I need to establish the connection between the directive and these two drop down boxes.
(Before I begin, I'd like to give credit where credit is due, Jonathan Wright: Angular JS - Mapquest)
<select ui-select2="select2Options" ng-model="LocationModel.State">
<option value=""></option>
<option ng-repeat="state in states" value="{{state.id}}">{{state.name}}</option>
</select>
<select ui-select2="select2Options" ng-model="LocationModel.City">
<option value=""></option>
<option ng-repeat="city in cities" value="{{city.id}}">{{city.name}}</option>
</select>
Here is my html directive template:
<map class="mapper" height="400" width="700"></map>
and here's the angular directive (this doesn't work)
mapapp.app.directive('map', function (logger) {
var directiveDefinitionObject = {
restrict: 'E',
template: '<div id="map"></div>',
link: function link(scope, element, attrs)
{
var map_height = attrs['height'] || 400;
var map_width = attrs['width'] || 400;
$('#map').css('width', map_width).css('height', map_height);
//somehow get the scope values to show up here every time
//the dropdown gets selected
var city = scope.LocationModel.City;
var state = scope.LocationModel.State;
/* do mapping logic here */
}
};
});
As you can see the gist of what I'm trying to do, I'm trying to make my directive recognize the dropdowns.
I'm thinking that my directive should have it's own ng-model, and that the value of the ng-model should reflect on the model's two drop downs, but I'm not exactly sure how to do that. I've looked around and wasn't able to find anything that'd help me out.
[Edit - 1/28/2014 - 7:13pm eastern time]
After following Dalorzo's advice, I created the following fiddlers:
Here is a jsfiddle of a $watch working in the controller:
http://jsfiddle.net/W4ZSQ/
However, when removing this watch and trying to use the $watch located in the directive, it doesn't work.
http://jsfiddle.net/W4ZSQ/1/
[Edit - 1/28/2014 - 10:52pm eastern time]
Figured out it out. Since I was calling LocationCtrl twice, I thought that the scope model will be shared between both html elements. Apparently this is not the case; what happens is that I create another instance of the scope model, where the scope will be updated for the drop down, but not the directive. By sharing them under one scope, the sees that the value "LocationModel.State" has been changed.
http://jsfiddle.net/W4ZSQ/2/
I found a resourceful link on how to have one controller communicate with another:
http://onehungrymind.com/angularjs-communicating-between-controllers/
This is what you need todo in your directive is use a new attribute that will be added to your html, something like:
data-bound-field="LocationModel.State"
For example:
<map class="mapper" height="400" width="700" data-bound-field="LocationModel.State"></map>
Then in your directive code:
scope.$watch(attrs.boundField,function(newValue,oldValue, scope){
/* do mapping logic here */
});

Resources