Two way binding of select input in a directive not working - javascript

I've created a directive for a select input to select a userId. The model binds from the directive to the rest of the view. However, when I set the id from the controller it doesn't seem to bind to select input in the directive.
Controller:
app.controller('MainCtrl', function($scope) {
// set userId to willem's Id
$scope.userId = 3;
});
Directive:
app.directive('selectUser', function() {
return {
restrict: 'E',
scope: {
ngModel: '='
},
controller: function($scope) {
$scope.users = [{
"id": 1,
"name": 'Tupac'
}, {
"id": 2,
"name": 'Biggie'
}, {
"id": 3,
"name": 'Willem'
}];
},
templateUrl: 'directive.html'
};
});
index.html
<body ng-controller="MainCtrl">
users:
<select-user ng-model="userId"></select-user>
userId = {{userId}}
</body>
directive.html
<select class='form-control focus' ng-model="ngModel">
<option value="">all users</option>
// doesn't bind from the controller. Supposed to show willem, instead of all users to start with.
<option ng-Repeat="user in users" value="{{user.id}}">{{user.name}}</option>
</select>
Working example on: http://plnkr.co/edit/c7eyoB

You should use ngOptions :
<select class='form-control focus' ng-model="ngModel" ng-options="user.id as user.name for user in users">
<option value="">all users</option>
</select>
Then the binding will work. See updated plnkr
Edit, concerning the following question in comment :
could you please explain, why it is not working with ng-repeat?
You can achieve the same visual result with :
<select class='form-control focus' ng-model="ngModel">
<option value="">all users</option>
<option ng-repeat="user in users" value="{{user.id}}" ng-selected="user.id === ngModel">{{user.name}}</option>
</select>
I.e. we added ng-selected="user.id === ngModel".
But this has some drawbacks. First, you are creating unneeded isolated scopes. And also, the values bound to the options are strings, i.e. you will actually select '1', '2' or '3' instead of 1, 2 or 3.

Related

Using AngularJs component mutiple times in ng-repeat

I have a drop down component in angularjs which when used single time works fine and the values are selected but if I use the same component multiple times in ng-repeat the drop downs are rendered but the values are not being selected. I am using the following code:
JS file:
angular.module('mainApp').component('inputSelectComponent', {
templateUrl : 'sys/templates/input-select.template.html',
bindings : {
ngModel : '='
},
controller: ['$scope', '$element', '$http', 'translate', 'apiServer', 'session', 'EVENTS', function compCategoriesDropdownController($scope, $element, $http, translate, apiServer, session, EVENTS) {
var _select = $element.find('select');
$scope.options = JSON.parse($element.attr('options'));
setTimeout(function(){
$element.find('select').selectize({});
}, 0);
$scope.$watch('$ctrl.ngModel', function(e){
var selectize = _select[0].selectize;
if (typeof(selectize) !== 'undefined') {
setTimeout(function(){
selectize.setValue(e);
}, 0);
}
});
$scope.$watch('$element.attr', function(e){
$scope.label = $element.attr('label');
if ($element.attr('disabled') === 'disabled'){
_select.attr('disabled', 'disabled');
}
});
$element.find('select').on('change', function(e){
$scope.$ctrl.ngModel = e.target.value;
});
}]
});
Template file:
<label class="col-form-label">{{label}}</label>
<select class="cat-drop{{(error!=null)? ' not-validated' :''}}">
<option ng-repeat="(key, value) in options" value="{{key}}">
{{value}}
</option>
</select>
This is how I am calling it:
<div ng-repeat="relatedproduct in relatedproductsData">
<span class="serial-number">{{ $index + 1 }}.</span>
<div class="form-group row">
<input-select-component ng-model="relatedproduct.item_type" class="input-component col-sm-2" label="{{t('related_products_item_type')}}" options='{"2": "2 - For Product", "3": "3 - For Category"}'></input-select-component>
</div>
</div>
Any help is appreciated!
I used a directive instead of an component.
The documentations says:
Components only control their own View and Data: Components should never modify any data or DOM that is out of their own scope. ..
Using a directive allows you to usa ng-model="ngModel" on the select component in the directive and change the value directly.
angular.module('mainApp').directive('inputSelectComponent', function () {
return {
template: `<label class="col-form-label">{{label}}</label>
<select class="cat-drop{{(error!=null)? ' not-validated' :''}}" ng-model="ngModel">
<option ng-repeat="(key, value) in options" value="{{key}}">
{{value}}
</option>
</select>`,
scope: {
ngModel: '=',
options: '=',
}
}
});
it does work in the caller:
<div ng-repeat="relatedproduct in [{ item_type : '' }, {item_type : ''}, { item_type: '' }]">
<span class="serial-number">{{ relatedproduct }}.</span>
<div class="form-group row">
<input-select-component ng-model="relatedproduct.item_type"
class="input-component col-sm-2"
label="{{t('related_products_item_type')}}"
options='{"2": "2 - For Product", "3": "3 - For Category"}'></input-select-component>
</div>
</div>
Good luck!
Try using $timeout instead of setTimeout. Maybe because of angularJS digest...

Pre-Select the only item in dropdown AngularJS

I am trying to create an attribute directive (because I need to use this logic at multiple places in my project) for Select dropdown which automatically selects the only item if the items array has only one element.
I have the following HTML with an attribute directive named smart-select-dropdown:
<select class="form-control" ng-model="selectedItem" smart-select-dropdown
ng-options="item as item.name for item in items | filter: {someFilter}">
<option value="" ng-bind="Select Item"></option>
</select>
Here is my directive code:
angular.module('app').directive('smartSelectDropDown', function(){
return{
restrict: 'A',
replace: true,
link: function(scope, element){
angular.element(document).ready(function () {
if(element[0].options.length === 2){
element.find('select option:nth-child(2)').prop('selected', true);
}
});
}
}
});
Here is the controller code:
angular.module('app').controller('MyCtrl', function($scope){
$scope.selectedItem = null;
$scope.items = [
{
id : 1,
name : 'Item 1'
},
{
id : 2,
name : 'Item 2'
},
{
id : 3,
name : 'Item 3'
}
];
});
The problem I am facing is that element[0].options.length is not giving me the correct length. It is always giving me 1 which is actually the default 'Select Item' option. It does not contain the length of ng-options which is actually required. Can anyone let me know what am I missing here?
PS: I am using AngularJS version 1.2.17
This can be achieved without a directive.
Controller Code changes:
angular.module('app').controller('MyCtrl', function($scope){
$scope.items = [
{
id : 1,
name : 'Item 1'
},
{
id : 2,
name : 'Item 2'
}
]
});
$scope.selectedItem = items[1]; // or items[0] as per your requirement
HTML Code Changes:
<select class="form-control" ng-model="selectedItem"
ng-options="item as item.name for item in items track by item.id">
<!-- You may choose to remove this first option tag with in select -->
<option value="" ng-bind="Select Item"></option>
</select>
There is even no need of a directive in your case, you can just check the $scope.items length and assign the first element to ng-model if its length is 1
Controller:
angular.module('app').controller('MyCtrl', function($scope){
$scope.selectedItem = null;
$scope.items = [
{
id : 1,
name : 'Item 1'
},
{
id : 2,
name : 'Item 2'
}
]
if($scope.items.length == 1){
$scope.selectedItem = $scope.items[0];
}
});
HTML:
<select class="form-control" ng-model="selectedItem" ng-options="item as item.name for item in items">
<option value="" ng-bind="Select Item"></option>
</select>
You can get the selectedItem and items from the controller to your directive scope, and check the condition and set selectedItem accordingly.
angular.module('app').directive('smartSelectDropDown', function(){
return{
restrict: 'A',
replace: true,
scope: {
items: '=',
selectedItem: '=selectedItem'
}
link: function(scope){
if(scope.items.count == 1){
scope.selectedItem = scope.items[0];
}
}
});
}
}
});
Though, you might have to modify the code for some corrections as I have not tested it.
Note: if you are using directive just for this purpose, then follow #Rangamannar answer.

ng-repeat show default empty value for first option

The ng-repeat show default empty string for first option. Why in may case it does not work. What is wrong in here?
My angular code is here. What i mistake here. I don't understand.
var exchange = angular.module('app', []);
exchange.controller('ExchangeController', ExchangeController);
function ExchangeController($scope, $http) {
$http
.get(window.location.origin + "/api/get-item/", {
transformRequest: angular.identity,
headers: {'Content-Type': undefined, 'Process-Data': false}
})
.then(function(response){
$scope.items = response.data.items;
$scope.send_item_id = $scope.items[0].value;
});
$scope.getSendItem = function() {
var send_item_id = $("#send_item_id").val();
console.log(send_item_id);
}
}
Here is my html code,
<div class="form-group">
<div class="col-md-12 col-sm-12">
<select data-plugin-selectTwo
name="send_item_id"
id="send_item_id"
ng-model="send_item_id"
ng-change="getSendItem()"
class="form-control populate">
<option ng-repeat="item in items" value="#{{ item.value }}">#{{ item.text }}</option>
</select>
</div>
</div>
You need to manually select a "selected" item (itemSelected model in my solution) and change your value attribute to ng-value to make it work in the AngularJS way. Please check this demo fiddle I've created for you. Btw. you don't need jQuery here: please check how I log the selected item in getSendItem(). This will work for AngularJS 1.6.x or later.
View
<div ng-controller="MyCtrl">
<select data-plugin-selectTwo
name="send_item_id"
id="send_item_id"
ng-model="itemSelected"
ng-change="getSendItem()"
class="form-control populate">
<option ng-repeat="item in data" ng-value="item.value">
#{{ item.text }}
</option>
</select>
</div>
AngularJS application
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope) {
$scope.data = [{
"text": "Bkash",
"value": 1
}, {
"text": "Paypal",
"value": 2
}];
$scope.itemSelected = $scope.data[0].value;
$scope.getSendItem = function() {
console.log($scope.itemSelected);
}
});
While using AngularJS 1.5.x or earlier you need to do it like in this demo fiddle by using ng-selected:
View
<div ng-controller="Controller">
<select name="send_item_id"
id="send_item_id"
ng-model="itemSelected"
ng-change="getSendItem()">
<option
ng-repeat="item in data" value="{{ item.value }}"
ng-selected="{{item.value === itemSelected}}">
{{ item.text }}
</option>
</select>
</div>
AngularJS application
var myApp = angular.module('app', []);
myApp.controller('Controller', function($scope) {
$scope.data = [{
"text": "Bkash",
"value": 1
}, {
"text": "Paypal",
"value": 2
}];
$scope.itemSelected = String($scope.data[0].value);
console.log($scope.itemSelected);
$scope.getSendItem = function() {
console.log($scope.itemSelected);
}
You need to match the option value and ng-model value in order to prevent the default empty space. in your case it does not match since there is # symbol in front of your option value. remove it.
<option ng-repeat="item in items" value="{{ item.value }}">#{{ item.text }}</option>
Try this. It's working for me.
<option value="{{item.value}}" ng-repeat="item in items" ng-selected="$first">{{ item.text }}</option>

Angular watch changes on another instance from the same directive

I'm trying to implement the classic Country > State > City combobox using angular directives.
I'm willing to link them by a custom attribute pair name/depends-on, but I don't know if there's a better form.
My HTML is the following:
<div ng-app="app" ng-controller="Ctrl">
<multiselect source-url="/countries.json" auto-init="true" name="Contries">
<select multiple ng-model="$parent.selectedOptions" ng-change="$parent.handleChange()">
<optgroup ng-repeat="(continent, countries) in $parent.optionList" label="{{continent}}">
<option ng-repeat="country in countries" value="{{country.code}}">
{{country.code}} - {{country.name}}
</option>
</optgroup>
</select>
</multiselect>
<multiselect source-url="/states.json" depends-on="Countries" name="States">
<select multiple ng-model="$parent.selectedOptions" ng-change="$parent.handleChange()">
<optgroup ng-repeat="(continent, countries) in $parent.optionList" label="{{continent}}">
<option ng-repeat="country in countries" value="{{country.code}}"> {{country.code}} - {{country.name}}
</option>
</optgroup>
</select>
</multiselect>
</div>
The Javascript:
app.directive('multiselect', function () {
return {
restrict: 'E',
transclude: true,
scope: {
sourceUrl : '#',
autoInit : '#',
dependsOn : '#'
},
controller: ['$scope', '$element', '$attrs', '$transclude', '$http', function ($scope, $element, $attrs, $transclude, $http) {
$scope.handleChange = function handleChange() {
console.log($scope.selectedOptions)
}
console.log($scope.dependsOn)
function updateSource() {
const config = {
params : {}
}
if ($scope.dependsOn) {
// how do I get dependsOnValue?????
config.params[$scope.dependsOn.toLowerCase()] = $scope.dependsOnValue
}
$http.get($scope.sourceUrl, config)
.then(function (response) {
$scope.optionList = response.data
})
}
if ($scope.autoInit) {
updateSource()
}
}],
template: '<ng-transclude></ng-transclude>'
}
})
The question is: how do I watch for changes in Countries in order to get its value to update States?
I'm trying to make this as reusable as possible.

Dynamic model binding in nested directives working for input values, but not select

I have a custom directive being used like so:
<drop-down-filter-field
model="my_model.property"
options="{ list: property_options, field: 'code' }"
></drop-down-filter-field>
With the directive code (field is another directive that groups together some common markup for errors and layout etc):
<field label="label" field="field" model="model" display="display" annotation="annotation">
<select
ng-model="$parent.model"
ng-change="setValue(item.value, item[options.field])"
>
<option ng-repeat="item in options.list" value="{{item.value}}">{{item[options.field]}}</option>
</select>
</field>
I tried to add the ng-change to manually set my model value, since the drop down isn't working. But, the function in ng-chang isn't firing either.
And Directive
angular.module('my-app')
.directive('dropDownFilterField', function($compile) {
return {
restrict: 'E',
templateUrl: 'views/directives/form-elements/drop-down-filter-field.html',
controller: 'dropDownFilterField',
scope: {
model: '=',
label: '=',
field: '=',
options: '=',
annotation: '=',
}
};
})
.controller('dropDownFilterField', function($scope, $element, $parse) {
$scope.setValue = function(value) {
self.$scope.model = value;
};
})
;
I already have this pattern with the nested directive with "$parent.model", functioning in several other templates/directives, for example, an input tag:
<field label="label" field="field" model="model" annotation="annotation" validate="validate">
<input type="text" name="{{formname}}" ng-model="$parent.model" ng-required="{{required}}" placeholder="{{field.default}}"
uib-typeahead="{{'typeAheadItem' + (options.field ? '.' + options.field : '')}} as {{'typeAheadItem' + (options.field ? '.' + options.field : '')}} for typeAheadItem in options.list | filter:$viewValue | limitTo:{{options.limit || 8}}"
ui-validate="'validateFunction($value)'"/>
</field>
All of my other custom directives that contain inputs, and textareas are working, but I can't get it working for select. Anyone know why this might be ?
Have you tried using ng-options?
<select
ng-model="$parent.model"
ng-options="item.value as item[options.field] for item in options.list"
>
</select>
There should be no need to use ng-change to update ng-model value, the two-way-binding of ng-model should update itself.

Categories

Resources