web-dev-qa-db-fra.com

Directive select2 angularjs personnalisée

J'ai créé une directive simple et personnalisée AngularJs pour ce plugin génial jquery jQuery-Select2 comme suit:

Directive

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

Utilisation dans les modèles 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>

Il fonctionne comme prévu et mon élément select normal est remplacé par les plugins select2

Cependant, il y a un problème cependant, parfois, il affiche la valeur par défaut i.e Select Country ici bien que dans la liste déroulante, la valeur de modèle appropriée soit sélectionnée automatiquement.

Maintenant, si j'augmente $timeout intervalle de 200 à une valeur élevée, par exemple 1500, cela fonctionne mais retarde le rendu de la directive. De plus, je pense que ce n'est pas une solution appropriée, car mes données sont chargées via ajax.

J'ai aussi essayé de mettre à jour la directive comme suit, mais je n’ai pas eu de chance non plus:

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: Je sais qu’il existe un module similaire ui-select , mais il nécessite un balisage différent sous la forme de <ui-select></ui-select>, et mon application est déjà entièrement développée et je souhaite simplement remplacer la zone de sélection normale par select2.

Alors pouvez-vous s'il vous plaît me guider, comment puis-je résoudre ce problème et m'assurer que cette directive reste en phase avec le dernier comportement?

16
Kalpesh Patel

C'est peut-être plus simple que prévu!

S'il vous plaît jeter un oeil à ceci Plunker

En gros, tous les plugins, Angularjs $ watch doivent être basés sur quelque chose. Je ne suis pas sûr à 100% pour jQuery-select2; mais je pense que c'est juste les événements DOM normaux du contrôle. (Et dans le cas de Angular $ watch, il s’agit d’une "boucle de vérification sale")

Mon idée est de faire confiance à jquery-Select2 et à AngularJS pour gérer ces événements de changement.

Nous avons juste besoin de surveiller les changements dans les manières d'Angular et de mettre à jour l'option Select dans les manières de Select2

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

//...

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

Remarque: J'ai ajouté 2 nouvelles montres que vous aimeriez avoir!

7
Mr. Duc Nguyen

Angular ne voudra pas que les données de modèle soient modifiées par un plugin tiers. Mon hypothèse est basée sur le fait que votre utilisation de $ timeout est une condition de concurrence critique entre Angular mettant à jour les options ou le modèle et le plugin select2. La solution que j'ai proposée consiste à retirer la mise à jour principalement des mains d'Angular et à la faire manuellement à partir de la directive, afin de vous assurer que tout correspond, peu importe qui modifie. Voici la directive que je suis venu avec:

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

        }
    };
});

J'ai ajouté aux attributs select-options et select-model. Celles-ci seront utilisées pour renseigner et mettre à jour les données à l'aide de l'interface de select2. Voici un exemple de code 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>

Veuillez noter qu'il est encore possible de nettoyer la directive et que certaines choses sont supposées à propos de l'entrée, telles que le "in" dans l'attribut select-options. Cela n'impose pas non plus les attributs mais échoue s'ils n'existent pas.

Veuillez également noter que j'ai utilisé Select2 version 4, comme en témoigne la el.val(i).trigger("change"). Vous devrez peut-être inverser certaines choses si vous utilisez une version plus ancienne.

Voici la jsfiddle demo de directive en action.

3
Uncharted Space

Je ne connais pas très bien select2 (de sorte que l'API permettant d'obtenir et de définir la valeur affichée dans le contrôle peut être incorrecte), mais je suggère ceci comme alternative:

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

Le premier $ timeout est nécessaire car vous utilisez ng-options. Par conséquent, les options ne seront pas dans le DOM avant le prochain cycle de résumé. Le problème, c'est que de nouvelles options ne seront pas ajoutées au contrôle si le modèle de pays est modifié ultérieurement par votre application. 

3
Joe Enzminger

Il ne répond pas directement à votre question, mais veuillez le prendre, car certaines personnes souhaitent adopter une autre approche plutôt que de s'en tenir à jQuery select2.

J'ai construit le mien à cette fin car je n'étais pas satisfait des systèmes existants qui ne suivent pas exactement les principes de Angular, HTML-first.

Il est encore tôt, mais je pense que toutes les fonctionnalités fonctionnent dans tous les navigateurs modernes.

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

Ce sont des exemples

1
allenhwkim

J'ai essayé de reproduire votre problème et il semble bien fonctionner. voici le violon je suis venu avec:

http://jsfiddle.net/s24gLdgq/

Vous pouvez avoir un comportement différent en fonction de la version de Angular et/ou de Select2 que vous utilisez, pouvez-vous le spécifier?

De même, si vous souhaitez éviter les scintillements, veillez à masquer la balise <select> par défaut afin que rien ne s'affiche avant que l'élément select2 ne sorte.

Cela se fait aussi dans mon jsfiddle avec le CSS

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