用 angularjs 自定义指令包装 ui-select

Wrapping ui-select with angularjs custom directive

是的,在 SO 上有几个类似的问题,但是 none 似乎解决了我的问题(或者使用 Angular 1.5.8 和 [=53= 的最新版本] UI-Select 1.4.x).

我遇到的问题是双向数据绑定。当我尝试将我的模型绑定到 ui-select 时,如果我在没有包装 ui-select 的指令的情况下这样做,它就可以工作。但是,当我使用包装 ui-select 时,它会朝一个方向更新,直到我在指令中修改模型。

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>

标签-输入-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);
                   }
                });
            }
        };
    }
})();

标签-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);

我在这里创建了问题的 Plunker: https://plnkr.co/edit/Yn1qhMjKuij7FM8Unpad

请注意,当您向顶部控件添加标签时,它会更新底部控件中的数据模型。但是,当您将标签添加到底部控件时,它会停止更新数据模型。我怀疑这与使用 ng-model.

绑定模型的方式有关

非常感谢任何帮助。

FWIW,这个线程与我的问题最相似: 然而,当我尝试模仿这个解决方案时,它只会让我达到现在的状态。

所以我找到了一个解决方案,我有点理解"why"它有效但不能 100% 确定为什么。

所以首先,我简化了指令:

标签-输入-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;
            }
        };
    }
})();

我所做的就是将传入的范围分配给控制器函数中范围本身的 属性:

controller: function($scope) {
  $scope.innerModel = $scope;
}

然后我更新了模板中对 ngModel 的引用以使用 innerModel.ngModel:

标签-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>

这里有一个 link 功能正常的 Plunkr:https://plnkr.co/edit/Eq9pIl8KoHZ2PuTa2PLu?p=preview

我怀疑 ui-select 中的某些东西正在破坏范围,但不确定如何在不付出大量额外努力的情况下证明或追踪它。