Comparaison d'égalité d'objet pour entrée avec ng-model et ng-value

Permettez-moi de commencer en disant que cette question est très similaire aux problèmes liés à la sélection dans une <select> l'aide de ng-options. Par exemple, Travailler avec la sélection à l'aide des ng-options d'AngularJS . Le problème spécifique consiste à comparer deux instances différentes d'un objet qui n'est pas égal à la référence, mais qui représentent logiquement les mêmes données.

Pour démontrer, disons que nous avons le tableau suivant d'options et la variable d'option sélectionnée dans le modèle:

 $scope.items = [ {ID: 1, Label: 'Foo', Extra: 17}, {ID: 2, Label: 'Bar', Extra: 18}, {ID: 3, Label: 'Baz', Extra: 19} ]; $scope.selectedItem = {ID: 1, Label: 'Foo'}; 

Notez que les objets ci-dessus sont juste pour la démonstration. J'ai spécifiquement laissé la propriété 'Extra' sur selectedItem pour montrer que parfois mes objets de modèle diffèrent dans leurs propriétés spécifiques. L'important, c'est que je veux comparer sur la propriété ID. J'ai une fonction equals() sur mes objets réels qui compare à la fois le prototype de «classe» et l'ID.

Et ensuite, dans la vue:

 <label class="radio inline" ng-repeat="item in items"> <input type="radio" ng-model="selectedItem" ng-value="item"> {{item.Label}} </label> 

Maintenant, le problème ici est que le bouton radio pour 'Foo' ne démarre pas, parce que angular utilise l'égalité de référence pour les objets. Si j'ai changé la dernière ligne dans mon champ d'application pour ce qui suit, tout fonctionnerait comme prévu.

 $scope.selectedItem = items[0]; 

Mais, le problème que je présente, c'est que dans ma demande, je ne déclare simplement pas ces deux variables simples. Plutôt, la liste des options et la structure de données où l'option sélectionnée est liée font partie des ensembles plus importants de données JSON interrogées à partir du serveur à l'aide de $ http. Dans le cas général, il est très difficile pour moi d'aller modifier la propriété sélectionnée liée aux données pour être l'option équivalente de ma requête de données.

Donc, ma question: dans les options ng pour <select> , angular offre une track by expression qui me permet de dire quelque chose comme "object.ID" et d'informer angulaire qu'il doit comparer la valeur du modèle sélectionné aux options via l'ID propriété. Existe-t-il quelque chose de similaire que je peux utiliser pour un tas d'entrées radio liées à la même propriété modèle? Idéalement, je pourrais dire à angular d'utiliser ma propre méthode personnalisée () que j'ai placée sur ces objets modèles, qui vérifie à la fois le type d'objet ainsi que l'ID. À défaut, cependant, la possibilité de spécifier une comparaison d'ID fonctionnerait également.

J'écris une directive très simple. Utilisation d'une sorte de "track-by" pour mapper deux objets différents. Voir http://jsfiddle.net/xWWwT/146/ .

HTML

 <div ng-app="app"> <div ng-app ng-controller="ThingControl"> <ul > <li ng-repeat="color in colors"> <input type="radio" name="color" ng-model="$parent.thing" ng-value="color" radio-track-by="name" />{{ color.name }} </li> </ul> Preview: {{ thing }} </div> </div> 

JS

 var app = angular.module('app', []); app.controller('ThingControl', function($scope){ $scope.colors = [ { name: "White", hex: "#ffffff"}, { name: "Black", hex: "#000000"}, { name: "Red", hex: "#000000"}, { name: "Green", hex: "#000000"} ]; $scope.thing = { name: "White", hex: "#ffffff"}; }); app.directive('radioTrackBy', function(){ return { restrict: "A", scope: { ngModel: "=", ngValue: "=", radioTrackBy: "@" }, link: function (ng) { if (ng.ngValue[ng.radioTrackBy] === ng.ngModel[ng.radioTrackBy]) { ng.ngModel = ng.ngValue; } } }; }); 

OK, donc après une nouvelle évaluation, j'ai décidé d'adopter une approche plus "mix-in", en remplaçant la directive ng-model par ma propre directive personnalisée. Ceci est très similaire à l'approche que j'ai utilisée pour créer une directive "liste des cases" basée sur cette réponse: https://stackoverflow.com/a/14519881/561604 .

 .directive('radioOptions', function() { // Apply this directive as an attribute to multiple radio inputs. The value of the attribute // should be the scope variable/expression which contains the available options for the // radio list. Typically, this will be the collection variable in an ng-repeat directive // that templates the individual radio inputs which have radio-options applied. In addition, // instead of the normal ng-model, use a selected-option attribute set to the same expression. // For example, you might use radio-options like this: // <label ... ng-repeat="item in collection"> // <input type="radio" ... ng-value="item" radio-options="collection" selected-option="myModel.myProperty"> // </label> // // See https://stackoverflow.com/questions/19281404/object-equality-comparison-for-inputradio-with-ng-model-and-ng-value // for the SO question that inspired this directive. return { scope: { radioOptions: '=', selectedOption: '=', ngValue: '=' }, link: function( scope, elem, attrs ) { var modelChanged = function() { if( jQuery.isArray(scope.radioOptions) ) { jQuery.each( scope.radioOptions, function(idx, item) { // This uses our models' custom 'equals' function for comparison, but another application could use // ID propeties, etc. if( typeof item.equals === 'function' && item.equals(scope.selectedOption) ) { elem.prop( 'checked', item === scope.ngValue ); } }); } }; scope.$watch( 'radioOptions', modelChanged ); scope.$watch( 'selectedOption', modelChanged ); var viewChanged = function() { var checked = elem.prop( 'checked' ); if( checked ) { scope.selectedOption = scope.ngValue; } }; elem.bind( 'change', function() { scope.$apply( viewChanged ); }); } }; }); 

Comme OP a demandé, voici un exemple de directive sur les boutons radio qui fonctionnera avec des objets complexes. Il utilise underscore.js pour trouver l'élément sélectionné parmi les options. C'est un peu plus compliqué qu'il le devrait, car il prend également en charge le chargement des options et de la valeur sélectionnée avec les appels AJAX.

Puisque je ne peux pas encore ajouter de commentaires, je dois répondre ici. La réponse de Dana a fonctionné correctement pour moi. Bien que j'aimerais souligner pour utiliser son approche, il faudrait mettre en œuvre la fonction «égale» sur les objets de la collection. Voir ci-dessous exemple:

 .controller('ExampleController', ['$scope', function($scope) { var eq = function(obj) { return this.id === obj.id; }; col = [{id: 1, name: 'pizza', equals: eq}, {id:2, name:'unicorns', equals: eq}, {id:3, name:'robots', equals: eq}]; $scope.collection = col; $scope.my = { favorite : {id:2, name:'unicorns'} }; }]); 

Voir le lien plunker.

Pourquoi n'utilisez-vous que l'ID pour le sélectionner comme ça?

 <input type="radio" ng-model="selectedItem" ng-value="item.ID"> {{item.Label}} 

Et puis, au lieu d'utiliser selectedItem vous pouvez écrire des items[selectedItem] .

Oh, et en jouant avec votre problème dans jsfiddle, j'ai remarqué à d'autres choses:

A.) Vous avez oublié d'ajouter un attribut de name à l'entrée.

B.) Ne jamais utiliser quelque chose sans un point en modèle ng. Si vous essayez de publier selectedItem avec {{selectedItem}} dehors du bloc ng-repeat, vous remarquerez que la valeur ne se met pas à jour lorsque vous choisissez un bouton radio. Ceci est dû à la création ng-repeat la portée d'un enfant.