Angular ng-model in `<select>` change function find dynamically - angularjs

Using Angular 1.x I have two groups of select boxes, both call a function on change:
ng-click="checkOneOptionSelected()"
In my controller I would like to know which group of select boxes called the function:
$scope.checkOneOptionSelected = function(value){
.. do something...
}
Is there a way to get the value of ng-model dynamically without passing a string as a parameter, perhaps using 'this' or similar. HArdcoding the value as a param will work but feels hacky?

When using the ng-model directive, the code should use the ng-change directive instead of ng-click.
<select ng-model="$ctrl.sel1" ng-change="$ctrl.updateSel('sel1',$ctrl.sel1)"
ng-options="...">
<option value="">Select sel1</option>
</select>
<select ng-model="$ctrl.sel2" ng-change="$ctrl.updateSel('sel2',$ctrl.sel2)"
ng-options="...">
<option value="">Select sel2</option>
</select>
To share the same update function, simply add arguments to indicate the source of the change.
this.updateSel = function (id, val) {
console.log(id,val);
//...
};
The ng-click directive fights with the ngModelController. The ng-change directive uses the $viewChangeListeners property of the ngModelController. The ngChange expression is only evaluated when a change in the input value causes a new value to be committed to the model.
For more information, see
AngularJS ng-change Directive API Reference.

You may pass builtin $event object (mentioned in ng-click reference in arguments), which is iQuery event object.
ng-click="checkOneOptionSelected($event)"
Then use its target field to reason about what was clicked:
$scope.checkOneOptionSelected = function(event){
.. do something with event.target, e.g...
if(event.target.id == 'Option123') {
...
}
}

Related

How to invoke a JQuery method after Angular select directive applies a model change?

I am trying to change the selection of David Stutz's bootstrap-multiselect via Angular ng-model:
<select ng-model="selection" multiple="multiple" id="my-example">
<option value="cheese">Cheese</option>
<option value="tomatoes">Tomatoes</option>
<option value="mozarella">Mozzarella</option>
<option value="mushrooms">Mushrooms</option>
<option value="pepperoni">Pepperoni</option>
<option value="onions">Onions</option>
</select>
The changes to the model only get applied to the underlying select element, but the bootstrap-multiselect doesn't get updated automatically. Looking at its documentation, this is expected: you are required to call multiselect('refresh') afterwards to propagate the changes:
$('#my-example').multiselect('refresh');
My question is:
How to invoke this method when the model changes after Angular is done updating the select element?
Since I need to access the element, I assume directives are the way to go. I was looking at decorators, which in theory I could use to modify the behavior of the built-in select directive, but I don't know how to get my code invoked at the right moment.
I've prepared a plunk to demo the issue:
I have two multiselects bound to the same model: a bootstrap-multiselect and a plain one
I initialize both with a default selection
The first button changes the selection. The plain multiselect is updated immediatelly, but the bootstrap-multiselect appears unchanged.
The second button shows the current model value in an alert.
The third button calls refresh on bootstrap-multiselect, which causes it to update. This is what I would like to get called automatically by Angular.
In the end I managed to solve my problem with a decorator. I based it on the Directive Decorator Example in AngularJS documentation.
Unlike ngHrefDirective from the example, selectDirective defines both preLink and postLink, therefore the compile override must also return both. I only needed to change postLink though, where $render is defined. In my version of the method I simply invoked the original method, which updates the select element, and called multiselect('refresh') afterwards, which was my original requirement:
app.config(['$provide', function($provide) {
$provide.decorator('selectDirective', ['$delegate', function($delegate) {
var directive = $delegate[0];
directive.compile = function() {
function post(scope, element, attrs, ctrls) {
directive.link.post.apply(this, arguments);
var ngModelController = ctrls[1];
if (ngModelController) {
originalRender = ngModelController.$render;
ngModelController.$render = function() {
originalRender();
element.multiselect('refresh');
};
}
}
return {
pre: directive.link.pre,
post: post
};
};
return $delegate;
}]);
}]);
I dont think you should mix that (why refresh data with jquery while the data is in angular's watch cycle?). You can do something like this with your options:
<option ng-value="Cheese.val">{{Cheese.text}}</option>
<option ng-value="Tomatoes.val">{{Tomatoes.text}}</option>
And handle the rest with Angular (maybe google for angular + multiselect)

Setting initial value of a select list generated through ng-repeat in angular

I am creating a select list from an associative array using ng-repeat.The ng-model of list is bound to a scope variable that has some initial value.The value of the options generated are the key of the array.
However, the list does not initialize with the value of the model.I thought it might have something to do with the asynchronous behaviour of ng-repeat, so i created a directive to capture the end of rendering and emit an event.This event is caught in the controller and is used to re-assign the value of the ng-model.
The event is caught correctly, but the update to the ng-model does not reflect.
If I attach a function to a button that updates the variable , it shows correctly in the list.
I know it is so because of the way ng-repeat works.How can i work around this automatically set the initial value after the list has been rendered.
Here is an example of my issue:
http://jsbin.com/gapemenova/1/edit?html,js,output
HTML:
<h1 align='center'>
{{value}}
<select ng-model='value' >
<option ng-repeat='(tid,groups) in templates' value='{{tid}}' on-last-repeat >{{tid}} </option>
</select>
<button ng-click='refresh()' >Refresh</button>
</body>
JS:
var app=angular.module('gobo',[]);
//Create A Directive To Detect The End Of ng-repeat cycle.
app.directive('onLastRepeat',function(){
return function(scope,element,attrs) {
if(scope.$last) { setTimeout(function(){
scope.$emit('onRepeatLast',element,attrs);
},10);
}
};
});
app.controller('goboCtrl',function($scope){
//Initial Value
$scope.value="2";
$scope.$on('onRepeatLast', function(scope, element, attrs){
$scope.value='3';
alert($scope.value); });
//Data Source For ng-repeat
$scope.templates={};
$scope.templates["1"]=[{"prop":"value1"}];
$scope.templates["2"]=[{"prop":"value2"}];
$scope.templates["3"]=[{"prop":"value3"}];
$scope.refresh=function(){
$scope.value="2";
};
}); //Controller ends
Check this working demo: JSBin
Simply change the option to:
<option ng-repeat='(tid,groups) in templates' value='{{tid}}'
ng-model="value" ng-selected="value === tid" on-last-repeat >{{tid}}</option>
Adding ng-model binds the selected value to $scope.value. Using ng-selected to update the selected item dynamically.
Explanations
Updating at the end of ng-repeat through $on is out of Angular $digest lifecycle, you need to run $scope.$apply to trigger a $digest cycle manually. Check JSBin
ngClick will trigger a $scope.$apply by default. Check ngClick definition in Angular 1.4.1 Line 23244:
scope.$apply(callback);
You should be using ng-options instead of using an ng-repeat to bind your options to your array. More on ng-options: https://docs.angularjs.org/api/ng/directive/ngOptions

How to use watch over selected options and add into an array in angularjs

// i wanna put watch on option selection and put inside an array multipleVlaue bt its not working.
//Html
<select ng-model="category1" ng-options="item1 for item1 in abms">
<option value="" ng-init="category1"></option>
</select>
//js
$scope.multipleVlaue = [];
$scope.$watch('category1', function(){
$scope.multipleVlaue.push($scope.category1);
}, true);
Here is the Code
What im doing wrong?
You are the victim of not Understanding the Scopes
In short, ng-repeat creates new child scope and primitive values like (Numbers, String, Boolean) when defined on parent scope doesn't update value from inside the child scope, so you need to use . dot in your ng-model
So your code would like this
// html
<select ng-model="obj.category1" ng-options="item1 for item1 in abms">
//js
$scope.$watch('obj.category1', function (val) {
if ($scope.obj.category1) {
$scope.multipleVlaue.push($scope.obj.category1);
}
console.log($scope.multipleVlaue);
}, true);
See fiddle

UI Bootstrap typeahead value to be used by other typeahead objects

I have a ui-bootstrap type-ahead that pulls the states from a JSON doc. It works. What I need is to take that selected value and use it in another objects to retrieve data based on that selection. When I call the $scope value of the input field with the type-ahead function the value isn't being pass? I guess,but when I use it as a drop-down select it works. What am I missing here?
State Type-ahead:
<input type=text ng-model="selectedState"typeahead="state.value for state in states | fileter: $viewValue | limitTo:3" ng-change="updateState()" placeholder="Enter State"/>
or
State Drop-down select:
<select ng-options="state.value for state in states" ng-model="selectedState" ng-change="updateState()"></select>
controller:
$scope.updateState = function(){
$scope.wCenters = wCenterFactory.get({state:$scope.selectedState.value});
};
Maybe try typeahead-on-select instead of ng-change? I think that passes the selected item in.

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