During my testing phase, it came to my attention that there was a $scope.watch
on the model for a specific form input. Surprisingly, I discovered that the watch function would execute three times - first with the correct object value, then with the string "Object object" representing the object, and finally with a null value. To resolve this issue temporarily, I implemented a quick fix by examining the data type of the newValue
- if it's a string, the model is reset to its previous value. However, despite this workaround being effective, I remain puzzled as to why simply changing the library could lead to such a setback.
If time permits, I plan to create a simplified test case to replicate this situation.
UPDATE 2: I stumbled upon this question, which sheds light on the root cause of the problem. It appears that setting a priority on the directive can trigger the render function.
The updated code snippet looks like this:
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 resolved the issue.
compile: function (tElm, tAttrs) {
......
We implemented this solution in our project and though it's not flawless (issues persist with data-binding in certain cases; Select2 tends to return strings instead of objects), we managed to make it functional.
UPDATE: I believe I identified the main problem in AngularUI's select2.js file.
The source code includes the following within 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 setup functioned well with older Angular versions. However, with Angular 1.2.5, this method failed to work as expected; the $render
function was already predefined by Angular internally, causing the custom function not to be invoked. Renaming controller.$render
to controller.$renderui
resolved the underlying issue. Here's my corrected version:
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 adjustment addressed several issues encountered with Select2 (utilized across the project) when binding to an ng-model (now, Select2 updates correctly when the ng-model changes), including the initial problem I encountered.
TLDR: AngularUI select2 attempts to define controller.$render
, but since that function is already internally defined by Angular 1.2.5, redefining it does not seem to be effective. The problem is solved by renaming the function.
I hope this information proves useful to someone.