Angularjs 自定义 select2 指令

Angularjs Custom select2 directive

我为这个很棒的 jquery 插件 jQuery-Select2 创建了简单的自定义 AngularJs 指令,如下所示:

指令

app.directive("select2",function($timeout,$parse){
    return {
        restrict: 'AC',
        link: function(scope, element, attrs) {
            $timeout(function() {
                $(element).select2();
            },200); 
        }
    };
});

HTML 模板中的用法:

<select class="form-control" select2 name="country"
data-ng-model="client.primary_address.country"
ng-options="c.name as c.name for c in client.countries">
     <option value="">Select Country</option>
</select>

它按预期工作,我的正常 select 元素被 select2 插件取代。

但是有一个问题,有时它会显示默认值,即 Select Country,尽管在下拉列表中正确的模型值是自动 selected.

现在,如果我将 $timeout 间隔从 200 增加到某个高值,比如 1500,它可以工作,但会延迟指令的呈现。我也认为这不是合适的解决方案,因为我的数据是通过 ajax.

加载的

我也尝试过如下更新指令,但也没有成功:

app.directive("select2",function($timeout,$parse){
    return {
        restrict: 'AC',
        require: 'ngModel',
        link: function(scope, element, attrs) {
            var modelAccessor = $parse(attrs.ngModel);
            $timeout(function() {
                $(element).select2();
            });
            scope.$watch(modelAccessor, function (val) {
                if(val) {
                    $(element).select2("val",val);
                }
            });
        }
    };
});

PS:我知道有类似的模块 ui-select,但它需要一些不同的标记 <ui-select></ui-select>,我的应用程序已经完全开发,我只是想用 select2.

替换普通的 select 框

那么你能指导我如何解决这个问题并确保该指令与最新行为保持同步吗?

我不太熟悉 select2(因此在控件中获取和设置显示值的实际 API 可能不正确),但我建议将其作为替代方法:

app.directive("select2",function($timeout){
    return {
        restrict: 'AC',
        require: 'ngModel',
        link: function(scope, element, attrs, model) {

            $timeout(function() {
                element.select2();
            });

            model.$render = function() {
                element.select2("val",model.$viewValue);
            }
            element.on('change', function() {
                scope.$apply(function() {
                    model.$setViewValue(element.select2("val"));
                });
            })
        }
    };
});

第一个 $timeout 是必需的,因为您使用的是 ng-options,因此直到下一个摘要周期,选项才会出现在 DOM 中。这样做的问题是,如果您的应用程序后来更改了国家/地区模型,则不会将新选项添加​​到控件中。

它没有直接回答你的问题,但请接受它,因为有些人想做另一种方法而不是坚持 jQuery select2。

我为此目的构建了自己的,因为我不满意现有的不完全遵循 Angular 原则,HTML-first.

它仍处于早期阶段,但我认为所有功能都适用于所有现代浏览器。

https://github.com/allenhwkim/angular-autocomplete

这些是例子

我试图重现您的问题,它似乎运行良好。这是我想出的fiddle:

http://jsfiddle.net/s24gLdgq/

根据您使用的 Angular and/or Select2 的版本,您可能会有不同的行为,您可以指定吗?

此外,如果您想防止闪烁,请务必隐藏默认的 <select> 标记,这样在弹出 select2 元素之前不会显示任何内容。

这也是在我的 jsfiddle 中用 CSS

完成的
.form-control { width: 200px; opacity: 0 }

它可能比你想象的要简单!

请看这个Plunker

基本上所有的插件,Angularjs $watch 都需要基于一些东西。我不是 100% 确定 jQuery-select2;但我认为这只是控件的正常 DOM 事件。 (在 Angular $watch 的情况下,它是 "dirty checking loop")

我的想法是让我们信任 jquery-Select2 和 AngularJS 来处理这些更改事件。

我们只需要观察 Angular 方式的变化并更新 Select2 方式中的 select

var refreshSelect = function() {
    if (!element.select2Initialized) return;
    $timeout(function() {
        element.trigger('change');
    });
};

//...

scope.$watch(attrs.ngModel, refreshSelect);

注意:我已经添加了2款新手表,我想你会想要的!

Angular 不喜欢第三方插件修改模型数据。我的猜测基于以下事实:您使用 $timeout 是 Angular 更新选项或模型与 select2 插件之间存在竞争条件。我想出的解决方案是将更新大部分从 Angular 手中拿走,并从指令中手动执行,这样您就可以确保无论谁在修改,一切都匹配。这是我想出的指令:

app.directive("select2",function($timeout,$parse){
    return {
        restrict: 'AC',
        link: function(scope, element, attrs) {
            var options = [],
                el = $(element),
                angularTriggeredChange = false,
                selectOptions = attrs["selectOptions"].split(" in "),
                property = selectOptions[0],
                optionsObject = selectOptions[1];
            // watch for changes to the defining data model
            scope.$watch(optionsObject, function(n, o){
                var data = [];
                // format the options for select2 data interface
                for(var i in n) {
                    var obj = {id: i, text: n[i][property]};
                    data.push(obj);
                }
                el.select2({data: data});
                // keep local copy of given options
                options = n;
            }, true);
            // watch for changes to the selection data model
            scope.$watch(attrs["selectSelection"], function(n, o) {
                // select2 is indexed by the array position,
                // so we iterate to find the right index
                for(var i in options) {
                    if(options[i][property] === n) {
                        angularTriggeredChange = true;
                        el.val(i).trigger("change");
                    }
                }
            }, true);
            // Watch for changes to the select UI
            el.select2().on("change", function(e){
                // if the user triggered the change, let angular know
                if(!angularTriggeredChange) { 
                    scope.$eval(attrs["selectSelection"]+"='"+options[e.target.value][property]+"'");
                    scope.$digest();
                }
                // if angular triggered the change, then nothing to update
                angularTriggeredChange = false;
            });

        }
    };
});

我已经添加到属性 select-optionsselect-model。这些将用于使用 select2 的界面填充和更新数据。这是一个示例 html:

<select id="sel" class="form-control" select2 name="country"
  select-selection="client.primary_address.country" 
  select-options="name in client.countries" >
     <option value="">Select Country</option>
</select>
<div>Selected: {{client.primary_address.country}}</div>

请注意,仍然可以对指令进行一些清理,并且有任何关于输入的假设,例如 select-options 属性中的 "in"。它也不强制执行这些属性,但如果它们不存在就会失败。

另请注意,我使用的是 Select2 版本 4,如 el.val(i).trigger("change") 所证明。 如果使用旧版本,您可能需要还原某些内容。

这是指令的jsfiddle demo