Yes, there are a couple of similar questions like this on SO, but none seem to address my issue (or work with recent versions of Angular 1.5.8 and Angular UI-Select 1.4.x).
The problem I'm having is with bi-directional data binding. When I try to bind my model to ui-select, if I do it without a directive wrapped ui-select, it works. However when I use the wrapped ui-select, it updated in one direction, until I modify the model within the directive.
index.html
<div class="form-group">
<label class="control-label" for="TEST">TEST</label>
<ui-select id="TEST" multiple="" ng-model="vm.selectedInstrument.gradeLevels" theme="bootstrap" close-on-select="false" append-to-body="true" style="min-width: 250px">
<ui-select-match placeholder="THIS IS A TEST">{{$item.name}}</ui-select-match>
<ui-select-choices repeat="opt in vm.gradeLevelList | filter:$select.search">
<div ng-bind-html="opt.name | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<span class="help-block">
{{vm.selectedInstrument | json}}
</span>
</div>
<tag-input ng-model="vm.selectedInstrument.gradeLevels" placeholder="Select one or more..." label-text="Grade Levels" options="vm.gradeLevelList" ele-id="gradeLevelTagInput"></tag-input>
tag-input-directive.js
(function () {
'use strict';
angular.module('surveyexplorer')
.directive('tagInput', tagInput);
function tagInput() {
return {
restrict: 'E',
templateUrl: 'tag-input.html',
scope: {
options: '=',
editMode: '=',
labelText: '@',
eleId: '@s',
placeholder: "@"
},
require: ['?ngModel'],
link: function (scope, elem, attrs, ctrls) {
var ngModelCtrl = ctrls[0];
ngModelCtrl.$render = function() {
scope.innerModel = ngModelCtrl.$viewValue;
};
scope.$watch('innerModel', function(newval, oldval){
if (newval !== oldval) {
ngModelCtrl.$setViewValue(newval);
}
});
}
};
}
})();
tag-input.html
<div class="form-group">
<label class="control-label" for="{{eleId}}">{{labelText}}</label>
<ui-select id="{{eleId}}" multiple ng-model="innerModel" theme="bootstrap" close-on-select="false"
append-to-body="true" style="min-width: 250px">
<ui-select-match placeholder="{{placeholder}}">{{$item.name}}</ui-select-match>
<ui-select-choices repeat="opt in options | filter:$select.search" class="scrollable-menu">
<div ng-bind-html="opt.name | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<span class="help-block">
{{innerModel}}
</span>
</div>
script.js
(function(angular) {
'use strict';
angular.module('surveyexplorer', [
'ngSanitize',
'ngAnimate',
'ui.bootstrap',
'ui.select',
])
.controller('InstrumentCtrl', [function() {
var vm = this;
vm.selectedInstrument = {
gradeLevels: []
};
vm.gradeLevelList = [{
"code": "IT",
"name": "Infant/toddler",
"sortOrder": 1000,
"gradeLevelId": 1
}, {
"code": "PR",
"name": "Preschool",
"sortOrder": 2000,
"gradeLevelId": 2
}, {
"code": "PK",
"name": "Prekindergarten",
"sortOrder": 3000,
"gradeLevelId": 3
}, {
"code": "TK",
"name": "Transitional Kindergarten",
"sortOrder": 4000,
"gradeLevelId": 4
}, {
"code": "KG",
"name": "Kindergarten",
"sortOrder": 5000,
"gradeLevelId": 5
}, {
"code": "1",
"name": "First grade",
"sortOrder": 6000,
"gradeLevelId": 6
}, {
"code": "2",
"name": "Second grade",
"sortOrder": 7000,
"gradeLevelId": 7
}, {
"code": "3",
"name": "Third grade",
"sortOrder": 8000,
"gradeLevelId": 8
}, {
"code": "4",
"name": "Fourth grade",
"sortOrder": 9000,
"gradeLevelId": 9
}, {
"code": "5",
"name": "Fifth grade",
"sortOrder": 10000,
"gradeLevelId": 10
}];
}]);
})(window.angular);
I've created a Plunker of the problem here: https://plnkr.co/edit/Yn1qhMjKuij7FM8Unpad
Notice that when you add tags to the top control, it updates the data model in the bottom control. However when you add tags to the bottom control, it stops updating the data model. I suspect it has to do with how the model is getting bound using ng-model
.
Any help graciously appreciated.
FWIW, This thread is the most similar to my problem: angularjs pass ngModel from wrapper directive to wrapped directive however when I try to mimic this solution, it only gets me to the point I am right now.
So I found a solution, which I somewhat understand "why" it works but not 100% sure why.
So first off, I simplified the directive:
tag-input-directive.html
(function () {
'use strict';
angular.module('surveyexplorer')
.directive('tagInput', tagInput);
function tagInput() {
return {
restrict: 'E',
templateUrl: 'tag-input.html',
scope: {
ngModel: '=',
options: '=',
editMode: '=',
labelText: '@',
eleId: '@s',
placeholder: "@"
},
controller: function($scope) {
$scope.innerModel = $scope;
}
};
}
})();
where all I do is assign the passed in scope to a property on the scope itself within a controller function:
controller: function($scope) {
$scope.innerModel = $scope;
}
then I updated the references to ngModel
in the template to use innerModel.ngModel
:
tag-input.html
<div class="form-group">
<label class="control-label" for="{{eleId}}">{{labelText}}</label>
<ui-select id="{{eleId}}" multiple ng-model="innerModel.ngModel" theme="bootstrap" close-on-select="false"
append-to-body="true" style="min-width: 250px">
<ui-select-match placeholder="{{placeholder}}">{{$item.name}}</ui-select-match>
<ui-select-choices repeat="opt in options | filter:$select.search" class="scrollable-menu">
<div ng-bind-html="opt.name | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<span class="help-block">
{{innerModel.ngModel}}
</span>
</div>
Here's a link to the functioning Plunkr: https://plnkr.co/edit/Eq9pIl8KoHZ2PuTa2PLu?p=preview
I suspect something within ui-select is clobbering the scope, but not quite sure how to prove or track that down without a lot of additional effort.