javascript Angularjs 自定义 select2 指令

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29644310/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 10:49:42  来源:igfitidea点击:

Angularjs Custom select2 directive

javascriptjqueryangularjsangularjs-directivejquery-select2

提问by Kalpesh Patel

I have created simple custom AngularJs directive for this awesome jquery plugin jQuery-Select2as follows:

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

Directive

指示

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

Usage in HTML templates:

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>

It is working as expected and my normal selectelement is replaced by select2plugins.

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

However there is one issue though, sometimes it is showing default value i.e Select Countryhere although in dropdown proper model value is auto selected.

然而,有一个问题,有时它会显示默认值,即Select Country这里虽然在下拉列表中自动选择了正确的模型值。

Now if I increase $timeoutinterval from 200to some high value say 1500, it is working but delays the the rendering of directive. Also I think this is not proper solution for it, as my data is getting loaded via ajax.

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

I have also tried to update directive as follows, but no luck in that either:

我还尝试按如下方式更新指令,但也没有运气:

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: I know that there is similar module present ui-select, but it requires some different markup in form of <ui-select></ui-select>, and my App is already fully developed and I just want to replace normal select box with select2.

PS:我知道有类似的模块存在ui-select,但它需要一些不同的标记形式<ui-select></ui-select>,我的应用程序已经完全开发,我只想用 select2 替换普通的选择框。

So can you please guide me how can I resolve this issue and make sure that directive keeps in sync with latest behaviour?

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

采纳答案by Mr. Duc Nguyen

It might be simpler than you expected!

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

Please have a look at this Plunker

请看看这个Plunker

Basically, all plugins, Angularjs $watch need to be based on something. I'm not 100% sure for jQuery-select2; but I think that's just the control's normal DOM events. (And in the case of Angular $watch, it is a "dirty checking loop")

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

My idea is that let's trust jquery-Select2 and AngularJS for handling those change events.

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

We just need to watch for change in Angular's ways and update the select in Select2's ways

我们只需要观察 Angular 方式的变化并以 Select2 的方式更新选择

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

//...

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

Notice:I have added in 2 new watch which I think you would like to have!

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

回答by Joe Enzminger

I'm not that familiar with select2 (so the actual API for getting and setting the displayed value in the control may be incorrect), but I suggest this as an alternative:

我对 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"));
                });
            })
        }
    };
});

The first $timeout is necessary because you are using ng-options, so the options won't be in the DOM until the next digest cycle. The problem with this is that new options won't be added to the control if the countries model is later changed by your application.

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

回答by Uncharted Space

Angular is not going to like have model data modified by a third party plugin. My guess based on the fact that your using $timeout is there is a race condition between Angular updating the options or the model and the select2 plugin. The solution I came up with is to take the updating mostly out of Angular's hands and do it manually from the directive, that way you can ensure everything is matching no matter who is modifying. Here's the directive I came up with:

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;
            });

        }
    };
});

I've added to attributes select-optionsand select-model. These will be used to populate and update the data using select2's interface. Here is a sample html:

我已经添加到属性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>

Please note there's still some cleanup that could be done to the directive and there are any things at assumes about the input, such as the "in" in the select-options attribute. It also doesn't enforce the attributes but just fails if they don't exist.

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

Also please note that I've used Select2 version 4, as evidenced by the el.val(i).trigger("change").You may have to revert some things if using an older version.

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

Here is the jsfiddle demoof directive in action.

这是指令在行动的jsfiddle 演示

回答by allenhwkim

It does not directly answer your question, but please take it as there are some people out there who wants to do another approach rather than sticking to jQuery select2.

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

I have built my own for this purpose because I was not satisfied the existing ones that are not exactly following Angular principles, HTML-first.

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

It's still early stage, but I think all features are working in all modern browsers.

它还处于早期阶段,但我认为所有功能都可以在所有现代浏览器中使用。

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

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

These are examples

这些是例子

回答by floribon

I tried to reproduce your issue and it seems to work well. here is the fiddle I came up with:

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

http://jsfiddle.net/s24gLdgq/

http://jsfiddle.net/s24gLdgq/

You may have a different behavior depending on the version of Angular and/or Select2 you are using, could you specify that?

根据您使用的 Angular 和/或 Select2 的版本,您可能有不同的行为,您能指定吗?

Also if you want to prevent flickering, be sure to hide the default <select>tag so nothing is displayed before the select2 element pops out.

此外,如果您想防止闪烁,请确保隐藏默认<select>标签,以便在 select2 元素弹出之前不显示任何内容。

This is also done in my jsfiddle with the CSS

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

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