Angularjs 自定义 select2 指令

Posted

技术标签:

【中文标题】Angularjs 自定义 select2 指令【英文标题】:Angularjs Custom select2 directive 【发布时间】:2015-06-21 01:33:09 【问题描述】:

我为这个很棒的 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,尽管在下拉列表中自动选择了正确的模型值。

现在,如果我将 $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 存在类似的模块,但它需要&lt;ui-select&gt;&lt;/ui-select&gt; 形式的一些不同标记,并且我的应用程序已经完全开发,我只想用select2替换普通选择框。

那么您能否指导我如何解决此问题并确保该指令与最新行为保持同步?

【问题讨论】:

这和select2有关系吗?如果您删除 select2 指令,并使其成为正常的选择元素,它是否按预期工作? 是的,如果我删除它,它会按预期工作。 我也在我的应用程序中使用select2,但我使用的是ui-select2,它是Angular 的包装器,现在已弃用。顺便说一句,Select2给我带来了很多悲伤,我建议你尽量避免:) @OmriAharon 使用ui-select2 比创建自己已经编写好的库更好。 @pankajparkar 是的,我建议使用推荐的ui-select 【参考方案1】:

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

请看看这个Plunker

基本上,所有插件,Angularjs $watch 都需要基于某些东西。我不是 100% 确定 jQuery-select2;但我认为这只是控件的正常 DOM 事件。 (对于 Angular $watch,它是一个“脏检查循环”)

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

我们只需要注意 Angular 方式的变化,并以 Select2 的方式更新选择

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

//...

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

注意:我已经添加了 2 款新手表,我认为您会喜欢!

【讨论】:

使用控制作为方法这行不通,您能否使其工作并将其包含在您的答案中?请参阅this plunker select 中的更改反映在范围变量中,但范围变量中的更改未反映在 select 中 您的 Plunkr 中有拼写错误。您应该更改按钮的 ng-click 以包括 cc。太 我强烈建议使用 'change.select2' 事件而不是 'change'。在我使用 undocumented event 'change.select2' 之前,我总是收到无限循环的更改事件 当模型为对象时,此解决方案不起作用。【参考方案2】:

我对 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 中。这样做的问题是,如果您的应用程序稍后更改国家模型,则不会将新选项添加​​到控件中。

【讨论】:

【参考方案3】:

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 的指令。

【讨论】:

【参考方案4】:

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

我为此目的构建了自己的,因为我不满意现有的那些不完全遵循 Angular 原则,HTML 优先。

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

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

这些是例子

Single Select Multi Select Custom Design Google Address Complete

【讨论】:

【参考方案5】:

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

http://jsfiddle.net/s24gLdgq/

根据您使用的 Angular 和/或 Select2 的版本,您可能会有不同的行为,您能具体说明一下吗?

另外,如果你想防止闪烁,一定要隐藏默认的&lt;select&gt; 标签,这样在select2元素弹出之前什么都不会显示。

这也是在我的 jsfiddle 中使用 CSS 完成的

.form-control  width: 200px; opacity: 0 

【讨论】:

以上是关于Angularjs 自定义 select2 指令的主要内容,如果未能解决你的问题,请参考以下文章

Vuejs自定义select2指令

AngularJS 自定义指令详解

AngularJS:directive自定义的指令

AngularJS--自定义指令和模板

angularJS中自定义指令

测试自定义验证 angularjs 指令