在 AngularJS 中使用 Select2(使用 MetroUI CSS)的依赖 drop-down 不工作

Dependent drop-down using Select2 (with MetroUI CSS) in AngularJS is not working

Select2 与 MetroUI CSS 配合 AngularJS 效果很好。 但是当我尝试使用相同的依赖下拉列表时,我遇到了 child drop-down 列表的视图没有相应更新以更改 parent drop-down 列表的问题.

这是一个堆栈代码段,展示了我遇到的问题。

(function () {
    "use strict";
    var app = angular.module("app", []);

    app.controller("AppCtrl", AppCtrl);

    function AppCtrl() {
        var vm = this;

        vm.ContextData = {
            selectParents: [{ "id": 1, "name": "item 1" },
                { "id": 2, "name": "item 2" },
                { "id": 3, "name": "item 3" },
                { "id": 4, "name": "item 4" }],
            selectChildren: [{ "id": 1, "parentId": 1, "name": "1 based on item1" },
                { "id": 2, "parentId": 1, "name": "2 based on item1" },
                { "id": 3, "parentId": 1, "name": "3 based on item1" },
                { "id": 4, "parentId": 1, "name": "4 based on item1" },
                { "id": 5, "parentId": 1, "name": "5 based on item1" },
                { "id": 6, "parentId": 1, "name": "6 based on item1" },
                { "id": 7, "parentId": 1, "name": "7 based on item1" },
                { "id": 8, "parentId": 2, "name": "8 based on item2" },
                { "id": 9, "parentId": 2, "name": "9 based on item2" },
                { "id": 10, "parentId": 2, "name": "10 based on item2" },
                { "id": 11, "parentId": 2, "name": "11 based on item2" },
                { "id": 12, "parentId": 2, "name": "12 based on item2" },
                { "id": 13, "parentId": 3, "name": "13 based on item3" },
                { "id": 14, "parentId": 3, "name": "14 based on item3" },
                { "id": 15, "parentId": 3, "name": "15 based on item3" },
                { "id": 16, "parentId": 3, "name": "16 based on item3" },
                { "id": 17, "parentId": 4, "name": "17 based on item4" },
                { "id": 18, "parentId": 4, "name": "18 based on item4" }]
        };            
    }

})();
<body ng-app="app" ng-controller="AppCtrl as vm">
    <div class="flex-grid">
        <div class="row">
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a parent" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedParent" ng-options="item.name for item in vm.ContextData.selectParents">
                        <option value=""></option>                        
                    </select>
                </div>
            </div>
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a child" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedChild" ng-options="item.name for item in vm.ContextData.selectChildren | filter:{parentId:vm.selectedParent.id}">
                        <option value=""></option>
                    </select>
                </div>
            </div>
        </div>
    </div>
    <div class="flex-grid margin10">
        <div class="row">
            <div class="cell colspan2">
                Selected Parent : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedParent}}
            </div>
        </div>
        <div class="row">
            <div class="cell colspan2">
                selected Child : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedChild}}
            </div>
        </div>
    </div>    
</body>

<link href="https://cdn.rawgit.com/olton/Metro-UI-CSS/master/build/css/metro-responsive.min.css" rel="stylesheet"/>
<link href="https://cdn.rawgit.com/olton/Metro-UI-CSS/master/build/css/metro.min.css" rel="stylesheet"/>

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.1/js/select2.min.js"></script>
<script src="https://cdn.rawgit.com/olton/Metro-UI-CSS/master/build/js/metro.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>

重现步骤:

  1. Select'Item 1'在parentselect(parentselect的place-holder是'select a parent' )
  2. 在 "Selected parent" 部分显示 selected parent 的正确值。
  3. Child select 绑定了正确的 child 选项。
  4. Select 来自 child select 的任何选项。
  5. 在 "Selected child" 部分显示 selected child 的正确值。
  6. 在Parentselect,select'Item 4'.
  7. 在 "Selected parent" 部分显示 selected parent 的正确值。
  8. Child select 已绑定,但选项未更新,它仍然显示 parent Item1.
  9. 的选项
  10. Select child select 中的任何项目,在 "Selected Child" 部分显示 child 的正确值,但 child 下拉列表显示错误的项目!

因此我的问题是,即使 child select 获得了正确的绑定选项,它的选项列表视图也不会刷新。

[我尝试了 select2.val(") 但 child 下拉列表的选项视图仍然没有刷新。]

另一个问题是:如果我清除 Parent select 中的 selection,它不会清除 Child 中的 selection ] select.

这里的问题涉及打开下拉列表后 Select2 如何缓存选项,因为它会将它认为正确的数据对象保存到 <option>,即使 AngularJS 实际上正在更改选项稍后的。结果,Select2 仍将显示缓存数据,而 AngularJS 将识别选项已更改,您最终会看到像这样的奇怪边缘情况。

目前没有解决此问题的已知方法,除了强制 AngularJS 以某种方式不重用相关下拉列表中的选项。

请参阅此 codepen.io 以使从属 drop-down 列表正常工作。

解决的要点是:

  1. 给child一个ID drop-down
  2. 然后提供一个ng-change事件给parentdrop-down
  3. 在 parent drop-down 的 ng-change 事件中,通过其 ID
  4. 搜索 child drop-down
  5. 然后调用child上的select2函数drop-down清空选中的值和html
  6. 不要忘记在 child drop-down 上触发更改事件。

所以在ng-change事件中会是这样的(childSelect2是child drop-down的ID):

var ele = angular.element('#childSelect2');            
ele.html("").val("").trigger("change");

另请注意,在单击 parent drop-down 中的叉号时,我们正在清除 child drop-down 对于我们正在调用的任何更改 ele.html("").val("").trigger("change");

这是预料之中的,因为如果 parent 中没有选择,那么在 child 中进行选择就没有意义。 但有了这个我们还需要清除 child drop-down 的 ng-model 因此在 ng-change 函数中设置 child drop-down 的模型为空(vm.selectedChild 是 ng-model for child drop-down)

vm.selectedChild = null;

虽然我发现 child 模型设置为 null 应该在调用 ele.html("").val("").trigger("change"); 之前进行,因此我将其作为 ng-change 事件中的第一条语句. [我不知道为什么这个 placement-order 很重要,但只有在这个改变之后它才有效:)]

希望回答对其他人有所帮助并节省他们的时间。

快速参考: HTML代码:

<body ng-app="app" ng-controller="AppCtrl as vm">
    <div class="flex-grid">
        <div class="row">
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a parent" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedParent" ng-options="item.name for item in vm.ContextData.selectParents" ng-change="vm.SetSelect2()">
                        <option value=""></option>                        
                    </select>
                </div>
            </div>
            <div class="cell colspan2 margin10">
                <div class="input-control full-size" data-role="select" data-placeholder="Select a child" data-allow-clear="true">
                    <select class="full-size" style="display:none" ng-model="vm.selectedChild" ng-options="item.name for item in vm.ContextData.selectChildren | filter:{parentId:vm.selectedParent.id}" id="childSelect2">
                        <option value=""></option>
                    </select>
                </div>
            </div>
        </div>
    </div>
    <div class="flex-grid margin10">
        <div class="row">
            <div class="cell colspan2">
                Selected Parent : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedParent}}
            </div>
        </div>
        <div class="row">
            <div class="cell colspan2">
                selected Child : 
            </div>
            <div class="cell colspan3">
                {{vm.selectedChild}}
            </div>
        </div>
    </div>    
</body>

JS代码(angular模块和控制器):

(function () {
    "use strict";
    var app = angular.module("app", []);

    app.controller("AppCtrl", AppCtrl);

    function AppCtrl() {
        var vm = this;

        vm.ContextData = {
            selectParents: [{ "id": 1, "name": "item 1" },
                { "id": 2, "name": "item 2" },
                { "id": 3, "name": "item 3" },
                { "id": 4, "name": "item 4" }],
            selectChildren: [{ "id": 1, "parentId": 1, "name": "1 based on item1" },
                { "id": 2, "parentId": 1, "name": "2 based on item1" },
                { "id": 3, "parentId": 1, "name": "3 based on item1" },
                { "id": 4, "parentId": 1, "name": "4 based on item1" },
                { "id": 5, "parentId": 1, "name": "5 based on item1" },
                { "id": 6, "parentId": 1, "name": "6 based on item1" },
                { "id": 7, "parentId": 1, "name": "7 based on item1" },
                { "id": 8, "parentId": 2, "name": "8 based on item2" },
                { "id": 9, "parentId": 2, "name": "9 based on item2" },
                { "id": 10, "parentId": 2, "name": "10 based on item2" },
                { "id": 11, "parentId": 2, "name": "11 based on item2" },
                { "id": 12, "parentId": 2, "name": "12 based on item2" },
                { "id": 13, "parentId": 3, "name": "13 based on item3" },
                { "id": 14, "parentId": 3, "name": "14 based on item3" },
                { "id": 15, "parentId": 3, "name": "15 based on item3" },
                { "id": 16, "parentId": 3, "name": "16 based on item3" },
                { "id": 17, "parentId": 4, "name": "17 based on item4" },
                { "id": 18, "parentId": 4, "name": "18 based on item4" }]
        };

        vm.SetSelect2 = function () {
            ////debugger;
            vm.selectedChild = null;
            var ele = angular.element('#childSelect2');
            //ele.html("");
            //ele.val("").trigger("change");  

            ele.html("").val("").trigger("change");
        }
    }

})();