Reputation:
I've recently updated a solution from Angular 1.2.0rc1 to Angular 1.2.5.
We're using AngularUI to provide Select2 ajax functionality for a dropdown. Before updating from Angular 1.2.0rc1, everything worked correctly; Select2 would handle the ajax call and load the dropdown data, and selecting an object would populate that form element.
Now that we've updated to 1.2.5, the ajax functionality is still loading the data - but making a selection doesn't update the ng-model attached to the input with the ui-select2
binding. As such, the form fails to validate (as the field is required) and the user cannot continue.
I've been looking into this, and one possible solution is to rewrite the solution to use a regular select2 dropdown, using <select>
and <option data-ng-repeat="each in myModel">
tags and using Angular's own AJAX functionality to append data to myModel
. However, I foresee issues with this (dealing with scrolling, etc) and I'm looking for a simpler, quicker solution.
Has anyone run into this issue before? Can anyone possibly shed some light on the situation?
Upvotes: 2
Views: 2726
Reputation:
During testing, I found that we had a $scope.watch
on the model for this form input. As it turns out, the watch function would run thrice - the first time, with the correct object value; the second time, with the string "Object object" as a representation of the object; and the third time, with a null value. I hotfixed this by checking the newValue
data type - if it's a string, reset the model to the old value. However, although this works, I'm still not sure why simply changing the library would have caused such a regression.
If I have time, I intend to try to reproduce this with a simplified test case.
UPDATE 2: I found this question, which explains the underlying reason this doesn't work. As such, it looks like it's possible to set a priority on the directive and have the render function called.
Code would be like so:
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout',
function (uiSelect2Config, $timeout) {
var options = {};
if (uiSelect2Config) {
angular.extend(options, uiSelect2Config);
}
return {
require: 'ngModel',
priority: 1, // This fixed it.
compile: function (tElm, tAttrs) {
......
We used this in our solution and although it's not perfect (there are still some hiccups with data-binding in some cases; Select2 loves to return strings instead of objects for some reason) we've been able to make it work.
UPDATE: I think I found the underlying issue in AngularUI's select2.js.
The source code defines the following as part of the directive for select2 under convertToSelect2Model
:
if (controller) {
// Watch the model for programmatic changes
scope.$watch(tAttrs.ngModel, function (current, old) {
if (!current) {
return;
}
if (current === old) {
return;
}
controller.$render();
}, true);
controller.$render = function () {
if (isSelect) {
elm.select2('val', controller.$viewValue);
} else {
if (opts.multiple) {
var viewValue = controller.$viewValue;
if (angular.isString(viewValue)) {
viewValue = viewValue.split(',');
}
elm.select2(
'data', convertToSelect2Model(viewValue));
} else {
if (angular.isObject(controller.$viewValue)) {
elm.select2('data', controller.$viewValue);
} else if (!controller.$viewValue) {
elm.select2('data', null);
} else {
elm.select2('val', controller.$viewValue);
}
}
}
};
This is all well and good in older versions of Angular. However, with Angular 1.2.5, this doesn't work; the $render
function is actually already defined by Angular and as such, the written function is never called. Renaming the controller.$render
function to controller.$renderui
fixed the underlying issue. This is my fix:
if (controller) {
controller.$renderui = function () {
if (isSelect) {
elm.select2('val', controller.$viewValue);
} else {
if (opts.multiple) {
elm.select2(
'data', convertToSelect2Model(controller.$viewValue));
} else {
if (angular.isObject(controller.$viewValue)) {
elm.select2('data', controller.$viewValue);
} else if (!controller.$viewValue) {
elm.select2('data', null);
} else {
elm.select2('val', controller.$viewValue);
}
}
}
};
// Watch the model for programmatic changes
scope.$watch(tAttrs.ngModel, function (current, old) {
if (!current) {
return
}
if (current == old) {
return
}
controller.$renderui();
}, true)
This fixed a lot of issues that I ran into with Select2 (used across my project) and binding to an ng-model (as now, Select2 will update correctly when the ng-model changes), including the original issue I had.
TLDR: AngularUI select2 tries to define controller.$render
, but that function is already defined internally by Angular 1.2.5 and attempting to redefine it doesn't seem to work. Renaming the function seems to solve the issue.
I hope that this helps someone.
Upvotes: 2