web-dev-qa-db-ja.com

Angularjsカスタムselect2ディレクティブ

次のように、この素​​晴らしい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プラグインに置き換えられています。

ただし、1つの問題があります。ドロップダウンでは適切なモデル値が自動的に選択されますが、デフォルト値、ここでは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:同様のモジュールが存在することを知っています i-select ですが、<ui-select></ui-select>の形式でいくつかの異なるマークアップが必要です。私のアプリはすでに完全に開発されているので、通常のコードに置き換えたいだけですselect2でボックスを選択します。

この問題をどのように解決し、ディレクティブが最新の動作と常に同期していることを確認できますか?

16
Kalpesh Patel

思ったより簡単かもしれません!

これを見てください Plunker

基本的に、すべてのプラグイン、Angularjs $ watchは何かに基づいている必要があります。 jQuery-select2について100%確信はありません。しかし、それは単なるコントロールの通常の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つ追加しました!

7
Mr. Duc Nguyen

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-options_および_select-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」など、入力に関して想定されるすべての事項があることに注意してください。また、属性を強制しませんが、存在しない場合は失敗します。

また、el.val(i).trigger("change")で証明されているように、Select2バージョン4を使用したことにも注意してください。 古いバージョンを使用している場合は、いくつかを元に戻す必要がある場合があります。

動作中のディレクティブの jsfiddleデモ を次に示します。

3
Uncharted Space

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

Ng-optionsを使用しているため、最初の$ timeoutが必要です。次のダイジェストサイクルまで、オプションはDOMにありません。この問題は、国のモデルがアプリケーションによって後で変更された場合、新しいオプションがコントロールに追加されないことです。

3
Joe Enzminger

それはあなたの質問に直接答えるものではありませんが、jQuery select2にこだわるのではなく、別のアプローチをしたいという人々がいるので、それを取ってください。

Angularの原則、HTML-firstに厳密に従っていない既存のルールに満足できなかったため、この目的のために独自に作成しました。

まだ初期段階ですが、すべての機能が最新のすべてのブラウザで機能していると思います。

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

これらは例です

1
allenhwkim

問題の再現を試みましたが、問題なく動作しているようです。ここに私が思いついたフィドルがあります:

http://jsfiddle.net/s24gLdgq/

Angularおよび/または使用しているSelect2のバージョンによって、動作が異なる場合があります。それを指定できますか?

また、ちらつきを防ぎたい場合は、デフォルトの<select>タグを非表示にして、select2要素が表示される前に何も表示されないようにします。

これはCSSを使用して私のjsfiddleでも行われます

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