I love Bootstrap-Select
and I am currently using it through the help of a directive made by another user joaoneto/angular-bootstrap-select
and it works as intended except when I try to fill my <select>
element with an $http
or in my case a dataService
wrapper. I seem to get some timing issue, the data comes after the selectpicker
got displayed/refreshed and then I end up having an empty Bootstrap-Select list.. though with Firebug, I do see the list of values in the now hidden <select>
. If I then go in console and manually execute a $('.selectpicker').selectpicker('refresh')
it then works.
I got it temporarily working by doing a patch and adding a .selectpicker('refresh')
inside a $timeout
but as you know it's not ideal since we're using jQuery
directly in an ngController...ouch!
So I believe the directive is possibly missing a watcher or at least something to trigger that the ngModel
got changed or updated.
Html sample code:
<div class="col-sm-5">
<select name="language" class="form-control show-tick"
ng-model="vm.profile.language"
selectpicker data-live-search="true"
ng-options="language.value as language.name for language in vm.languages">
</select>
<!-- also tried with an ng-repeat, which has the same effect -->
</div>
then inside my Angular Controller:
// get list of languages from DB
dataService
.getLanguages()
.then(function(data) {
vm.languages = data;
// need a timeout patch to properly refresh the Bootstrap-Select selectpicker
// not so good to use this inside an ngController but it's the only working way I have found
$timeout(function() {
$('.selectpicker, select[selectpicker]').selectpicker('refresh');
}, 1);
});
and here is the directive made by (joaoneto) on GitHub for Angular-Bootstrap-Select
function selectpickerDirective($parse, $timeout) {
return {
restrict: 'A',
priority: 1000,
link: function (scope, element, attrs) {
function refresh(newVal) {
scope.$applyAsync(function () {
if (attrs.ngOptions && /track by/.test(attrs.ngOptions)) element.val(newVal);
element.selectpicker('refresh');
});
}
attrs.$observe('spTheme', function (val) {
$timeout(function () {
element.data('selectpicker').$button.removeClass(function (i, c) {
return (c.match(/(^|\s)?btn-\S+/g) || []).join(' ');
});
element.selectpicker('setStyle', val);
});
});
$timeout(function () {
element.selectpicker($parse(attrs.selectpicker)());
element.selectpicker('refresh');
});
if (attrs.ngModel) {
scope.$watch(attrs.ngModel, refresh, true);
}
if (attrs.ngDisabled) {
scope.$watch(attrs.ngDisabled, refresh, true);
}
scope.$on('$destroy', function () {
$timeout(function () {
element.selectpicker('destroy');
});
});
}
};
}
One problem with the angular-bootstrap-select directive, is that it only watches ngModel
, and not the object that's actually populating the options in the select. For example, if vm.profile.language
is set to ''
by default, and vm.languages
has a ''
option, the select won't update with the new options, because ngModel
stays the same. I added a selectModel
attribute to the select, and modified the angular-bootstrap-select code slightly.
<div class="col-sm-5">
<select name="language" class="form-control show-tick"
ng-model="vm.profile.language"
select-model="vm.languages"
selectpicker data-live-search="true"
ng-options="language.value as language.name for language in vm.languages">
</select>
</div>
Then, in the angular-bootstrap-select code, I added
if (attrs.selectModel) {
scope.$watch(attrs.selectModel, refresh, true);
}
Now, when vm.languages
is updated, the select will be updated too. A better method would probably be to simply detect which object should be watched by using ngOptions
, but using this method allows for use of ngRepeat
within a select as well.
Edit:
An alternative to using selectModel
is automatically detecting the object to watch from ngOptions
.
if (attrs.ngOptions && / in /.test(attrs.ngOptions)) {
scope.$watch(attrs.ngOptions.split(' in ')[1], refresh, true);
}
Edit 2:
Rather than using the refresh
function, you'd probably be better off just calling element.selectpicker('refresh');
again, as you only want to actually update the value of the select when ngModel
changes. I ran into a scenario where the list of options were being updated, and the value of the select changed, but the model didn't change, and as a result it didn't match the selectpicker. This resolved it for me:
if (attrs.ngOptions && / in /.test(attrs.ngOptions)) {
scope.$watch(attrs.ngOptions.split(' in ')[1], function() {
scope.$applyAsync(function () {
element.selectpicker('refresh');
});
}, true);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With