I have this HTML
<select data-bind="value: $data.id">
<optgroup label="Regions" data-bind="foreach: countries.regions.names">
<option data-bind="html: $data.name, value: $data.value"></option>
</optgroup>
<optgroup label="Countries" data-bind="foreach: shipping.allCountries">
<option data-bind="html: $data.name, value: $data.code"></option>
</optgroup>
</select>
With this javascript which is being prefilled with an ID in either of the 2 optgroups. The ID is always present!
this.id = ko.observable(data.id);
But after the select is built by KnockOut, the value of this.id() is empty, and will return undefined. When I subscribe to the field, it gives me a notification it is being updated with nothing.
My current (dirty) fix:
setTimeout(function(){
self.id(data.id);
}, 500);
This works, but of course is not recommended.
Also, after selecting another element in the select and returning to the original value, it is fine too.
How do I fix this problem?
note: the correct element is selected.
The reason this fails is because the value of the <select> element is bound before its contents are bound using foreach and that the value binding will reset the model value to whatever is currently selected in the element if the model value can't itself be selected.
Example of problem: http://jsfiddle.net/mbest/8r6KY/
There are two solutions to this.
1. Prevent model value from being reset
One solution is to prevent the value binding from resetting the model value using a new binding option available in Knockout 3.1 (currently in Beta), valueAllowUnset.
<select data-bind="value: $data.id, valueAllowUnset: true">
Example: http://jsfiddle.net/mbest/D6dR2/
2. Binding contents before value
The second solution is to get the <option> elements in place before the value is bound. This can be done with a custom binding on the element that binds the contents early.
<select data-bind="bindInner, value: $data.id">
Binding:
ko.bindingHandlers.bindInner = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.applyBindingsToDescendants(bindingContext, element);
return { controlsDescendantBindings: true };
}
};
Example: http://jsfiddle.net/mbest/ETFsa/
Recommendation
The first option uses a built-in solution, but does have a downside that the UI selection happens using setTimeout (within the value binding). Thus you could have a visible change in the selection as the view is initialized. The second option is, I think, the better one, although you could, of course, use both of these together to doubly fix the problem.
Make sure that your countries.regions.names is actually pointing to the correct place. If those options don't exist then the values won't exist.
Related
The boolean value assigned to the model doesn't pre-select the corresponding option - shows first an empty option instead:
Select:
<select data-ng-model="scriptData.privacy" data-ng-options="privacyOption.value as privacyOption.label for privacyOption in privacyOptionsSelect track by privacyOption.value"></select>
Options in the controller:
$scope.privacyOptionsSelect=[
{
label:'Registered users only can view this',
value: false
},
{
label:'Anyone with the link can view this',
value: true
}
];
scriptData.privacy is set to false.
You should be careful when using the ngOptions expression with the format:
select as label for value in array
together with track by. This is because track by is applied to the value assigned to your ngModel, so if your selection is in the form privacyOption.value, the track by expression is actually applied to the value. This is the reason it doesn't successfully select the initial value.
To fix this you have two options. You can either just skip track by. This would work:
<select data-ng-model="scriptData.privacy"
data-ng-options="privacyOption.value as privacyOption.label for privacyOption
in privacyOptionsSelect"></select>
Or you could change the select as-expression to select the entire privacyOption-object. This would work as well (note the ngModel-directive changed as well):
<select data-ng-model="scriptData"
data-ng-options="privacyOption as privacyOption.label for privacyOption
in privacyOptionsSelect track by privacyOption.value"></select>
For a full (and probably better) explanation, I recommend the ngOptions documentation.
try ng-repeat instead of ng-options by applying the ng-repeat to option elements inside the select.
This is my Angular 2 Code:
HTML:
<select class="form-control" [(ngModel)]="wrapupData.assignToEmployee">
<option value="" disabled>Choose Status</option>
<option *ngFor="let emp of masterWrapupLov.employeeList"
[value]='emp.id'>{{emp?.name}}</option>
</select>
where wrapupData.assignToEmployee is a string, masterWrapupLov.employeeList is an array of Objects comprising id: string and name: string.
This code works perfectly fine selecting the option to make it reflect in the wrapupData.assignToEmployee model.
What I wanted to achieve is to select a default option when loading the DOM and update the model if the option is changed. I tried using [selected] comparing emp.id with the value I wanted to select, it did work in the UI but is not getting binded with the model as and when I change it. I tried using a default value using the model, that too is not working as it should and is not getting binded after the first default binding. I don't know if I'm clear enough, but I tried many methods that people have already suggested but nothing seems to work.
<select ng-model="myModel" id="searchAsset" class="search">
<option ng-repeat="asset in assettype" ng-click="assetclick()" value="{{asset}}"></option>
</select>
<select class="search" id="searchLevel">
<option class="chooseLevel" ng-repeat="level in levellist" value="{{level}}"></option>
</select>
While performing some logic on second dropdown, I want to fetch the selected value of the first dropdown or vice-versa. How can I do this?
I tried using $("#searchLevel").value and $("#searchLevel option:selected").text()
The direct answer to this question is to use the .val() method for jQuery like so:
var selectedVal = $("#searchLevel").val();
However, the slightly less direct, but true answer is that you should not be doing anything like this in an angular app - changes in the dom should only be affecting your view model.
When your using angular, jquery is really not required.
As per your code, The first select menu value will be stored in the ng-model attribute value i.e. myModel.
In your second select menu, specific the ng-model as well. You can just fetch the value of the drop down menu by calling ng-model name.
<select class="search" id="searchLevel" ng-model="secondSelect">
<option class="chooseLevel" ng-repeat="level in levellist" value="{{level}}"></option>
</select>
For example,
If you want to access the value inside your controller on change event, then
$scope.changeEventhandlerForFirstSelect = function() {
// $scope.myModel will contain the value
}
Similarly, for second select menu $scope.secondSelect will give that value.
try
$("#searchLevel").val();
with: $("#searchLevel option:selected").text() you get the text not the value.
I have a select element with an ng-model and ng-options on it. ng-options makes the possible selections dynamic however I have noticed that when ng-options updates the options ng-model is not also getting updated. For example if the select element has the value of "foo" selected then the ng-options model is updated and "foo" is no longer an option the selector updates to blank but ng-model still shows "foo" as its value. I would expect that ng-model would update as well to equal null. The only thing I can think of is to add a watch on items but that seems kind of lame. What is the best way to get ng-model to stay in sync with the select element in this scenario?
<div ng-controller="MyCtrl">
<p>Selected Item: {{foo}}</p>
<p>
<label>option</label>
<select ng-model="foo" ng-options="item.val as item.label for item in items">
<option value="">Select Something</option>
</select>
</p>
<button ng-click="remove()">Remove Second Item Option</button>
<p>{{items}}</p>
</div>
Here is the jsfiddle to illustrate the issue. http://jsfiddle.net/dberringer/m2rm8Lh6/2/
Note: I'm aware I could manually update the foo value with the delete method. This method is just to illustrate the issue.
Thanks for your feedback.
Update: I fixed a typo where referred to ng-options as ng-select.
change the button like
<button ng-click="remove(2)">Remove Second Item Option</button>
change the remove function
$scope.remove = function(removeIndex) {
if($scope.foo == removeIndex) {
$scope.foo = null;
}
$scope.items.splice(removeIndex-1,1);
};
here is the Demo Fiddle
Reason is,
ng-change is not going to trigger when,
if the model is changed programmatically and not by a change to the input value, check the Doc here
so u are not changing the select value by changing the select box instead do it using a button (programmatically) so angular will not trigger the change event on the select element and then angular doesn't know model is changed ,this might be the reason.
then what u need to do is change model value manually as $scope.foo = null;
I think angular didn't check that, once the ngOptions value changes, angular didn't do a check to see if the ngModel is exists in ngOptions.
angular.js
line 21371:
self.databound = $attrs.ngModel;
line 21828 - 21840:
return function (scope, element, attr) {
var selectCtrlName = '$selectController',
parent = element.parent(),
selectCtrl = parent.data(selectCtrlName) ||
parent.parent().data(selectCtrlName); // in case we are in optgroup
if (selectCtrl && selectCtrl.databound) {
// For some reason Opera defaults to true and if not overridden this messes up the repeater.
// We don't want the view to drive the initialization of the model anyway.
element.prop('selected', false);
} else {
selectCtrl = nullSelectCtrl;
}
You can see above code checks which option should be selected when generated the options, but i can't find a reverse check when ngOptions got updated.
I want to find the best practice to avoid an observable subscription from firing on page startup.
The Problem:
When you have something like the example described below, the alert will be displayed on startup, which is unwanted:
foo.someProperty = ko.observable(null);
foo.someProperty.subscribe(function () { alert(foo.someProperty()); });
Inconvenient Solution:
Until now I've been using the following solution:
foo.someProperty.subscribe(function () {
if (TriggersEnabled()) { alert(foo.someProperty()) };
});
The TriggersEnabled check avoids the callback execution, but this solution demands the creation of the TriggersEnabled property. Also it must be set as false before startup and as true after the complete page load.
The Question
Can I avoid this? Is there any other methods to achieve the same result?
Based on the fiddle you provided the event is firing because the default value you have bound to the select element is not a value in the list. So when the select list displays it changes the value of the observable it's bound to to the first option.
A way to fix this is to make sure the default value of the observable you bind to the select element is a valid value.
Example:
var page = new PageVM();
function PageVM () {
var vm = this;
vm.someProperty = ko.observable(''); //Provide a default value that will be in the select list
vm.someProperty.subscribe(function () { alert(vm.someProperty()); });
};
ko.applyBindings(page);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
foo.someProperty
<select data-bind="value: someProperty">
<!-- make sure the first option matches the default value of someProperty -->
<option value="">Select...</option>
<option value="01">Option 01</option>
<option value="02">Option 02</option>
</select>