【问题标题】:Object equality comparison for input[radio] with ng-model and ng-valueinput[radio] 与 ng-model 和 ng-value 的对象相等性比较
【发布时间】:2013-10-17 08:46:08
【问题描述】:

首先让我说这个问题与使用 ng-options 在<select> 标记中进行选择的问题非常相似。例如,Working with select using AngularJS's ng-options。具体问题是比较一个对象的两个不同实例,它们的引用不相等,但在逻辑上表示相同的数据。

为了演示,假设我们在模型中有以下选项数组和选定的选项变量:

$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'};

请注意,以上对象仅用于演示。我特意省略了selectedItem 上的“额外”属性,以表明有时我的模型对象的特定属性不同。重要的是我想比较 ID 属性。我的真实对象上有一个equals() 函数,用于比较原型“类”和 ID。

然后在视图中:

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

现在,这里的问题是“Foo”的单选按钮不会开始被选中,因为 Angular 正在使用对象的引用相等。如果我将范围中的最后一行更改为以下内容,一切都会按预期进行。

$scope.selectedItem = items[0];

但是,我遇到的问题是,在我的应用程序中,我并没有简单地在范围内声明这两个简单的变量。相反,选项列表和绑定所选选项的数据结构都是使用 $http 从服务器查询的较大 JSON 数据集的一部分。在一般情况下,我很难将数据绑定的选定属性更改为我的数据查询中的等效选项。

所以,我的问题: 在 &lt;select&gt; 的 ng-options 中,angular 提供了一个 track by 表达式,它允许我说类似“object.ID”的内容,并通知 angular 它应该通过 ID 属性将选定的模型值与选项进行比较。是否有类似的东西可以用于绑定到同一个模型属性的一堆无线电输入?理想情况下,我将能够告诉 Angular 使用我自己的自定义 equals() 方法,该方法已放置在这些模型对象上,该方法检查对象类型和 ID。如果做不到这一点,也可以指定 ID 比较。

【问题讨论】:

  • 我们使用 underscore.js 中的 findWhere 方法解决了这个问题,方法是在开始时从选项中选择值。我也想知道是否可以这样做。
  • 我没有使用 undescore.js,但我不介意滚动我自己的比较函数来做我需要的事情。您实际上在哪里雇用 findWhere?在自定义指令中?
  • 是的,我们有单选按钮,下拉指令就是为了这个。
  • 我很想知道您如何在解决方案中构建指令。这实际上可能是我的答案。
  • 这是radio button directive。它有点复杂,因为即使您使用 AJAX 调用加载选项,它仍然有效。你可能想让它更简单。

标签: javascript angularjs


【解决方案1】:

我写了一个最简单的指令。使用一种“track-by”来映射两个不同的对象。请参阅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;
            }
        }
    };
});

【讨论】:

【解决方案2】:

好的,所以经过进一步审查,我决定采用更“混合”的方法,本质上只是用我自己的自定义指令替换 ng-model 指令。这与我根据以下答案制作“复选框列表”指令的方法非常相似: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 );
            });
        }
    };
});

【讨论】:

    【解决方案3】:

    根据 OP 的要求,这里有一个示例 radio button directive,它将适用于复杂对象。它使用underscore.js 从选项中查找所选项目。它比应有的复杂一点,因为它还支持通过 AJAX 调用加载选项和选定的值。

    【讨论】:

    • 你硬编码了值比较器,你可以使用 angular.equals 代替
    【解决方案4】:

    为什么不直接使用 ID 进行选择呢?

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

    然后你可以写items[selectedItem],而不是使用selectedItem

    哦,在 jsfiddle 中处理您的问题时,我注意到了其他事情:

    a.) 您忘记在输入中添加 name 属性。

    b.) 永远不要在 ng-model 中使用没有点的东西。如果您实际上尝试在 ng-repeat 块之外输出带有{{selectedItem}} 的 selectedItem,您会注意到当您选择单选按钮时该值不会更新。这是由于ng-repeat 创建了自己的子作用域。

    【讨论】:

    • 是的,我看到这在我发布的有限示例中会起作用。但是,我遇到的问题是,在我的实际应用程序中,我确实使用了 ng-model 中另一个对象的属性。该属性需要使用实际对象实例进行维护,而不是其 ID。
    【解决方案5】:

    由于我还不能添加cmets,所以我必须在这里回复。达纳的回答对我来说没问题。虽然我想指出为了使用他的方法,但必须在集合中的对象上实现“等于”函数。见下例:

    .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'} };
    
     }]);
    

    请见机友link

    【讨论】:

    • 是的,这或多或少是我的代码的工作方式。如果您的所有对象都有一个“id”属性,那么在指令中修改这一行可能会更容易:if( typeof item.equals === 'function' &amp;&amp; item.equals(scope.selectedOption) ) { 简单地检查:if( item.id === scope.selectedOption.id ) { 但是,您可能需要在其中进行一些空检查,而不是当然。
    • 我实际上更喜欢 equals 函数,因为它为调用客户端提供了更多的灵活性来进行比较。想到命令模式的应用……
    猜你喜欢
    • 2015-08-31
    • 2019-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-26
    • 2017-11-11
    • 1970-01-01
    • 2019-04-28
    相关资源
    最近更新 更多