AngularJS 指令模板上的 ng-model

AngularJS ng-model on directive template

我有以下指令:

.directive('radioList', function ($ionicModal) {
    return {
        restrict: 'A',

        scope: {
            selectedItem: '=selectedItem',
            items: '=items'
        }
    };

使用以下模板 html:

    <div ng-repeat="item in items">
        <ion-radio ng-model="selectedItem" ng-value="item">{{item.toString()}}</ion-radio>
    </div>

虽然绑定在指令中,但 ng-model 无法在指令独立作用域或父作用域上正确更新。

如果我这样做:

.directive('radioList', function ($ionicModal) {
    return {
        restrict: 'A',

        scope: {
            selectedItemInternal: '=selectedItem',
            items: '=items'
        },
        link: function (scope, element, attr) {
            Object.defineProperty(scope, 'selectedItem', {
                get: function () {
                    return scope.selectedItemInternal;
                },
                set: function (value) {
                    scope.selectedItemInternal = value;
                }
            });
        }
    };

一切正常,我的 setter for selectedItems 被调用。

似乎是 Angular 中的错误?

更新:这是我的全部指令:

.directive('radioList', function ($ionicModal) {

    return {
        restrict: 'A',

        scope: {
            selectedItemInternal: '=selectedItem',
            items: '=items',
            header: '=header',
            showCancel: '=showCancel',
            listHide: '&listHide',
            doneText: '=doneText'
        },

        link: function (scope, element, attr) {

            element.css('cursor', 'pointer');

            var modal;

            scope.hide = function (result) {
                modal.remove();
                modal = null;
                if (scope.listHide) {
                    scope.listHide()(result, scope.selectedItem);
                }
            };

            // allow deselecting a radio button
            var isDeselecting = false; // event fires again after scope.selectedItem changes
            var hasChanged = false;
            scope.click = function (event) {
                if (!isDeselecting) {
                    hasChanged = scope.selectedItem != angular.element(event.target).scope().item;
                    isDeselecting = true;
                } else {
                    if (!hasChanged) {
                        scope.selectedItem = null;
                    }
                    isDeselecting = false;
                    hasChanged = false;
                }
            };

            // required to handle that click only fires once when double clicking
            scope.doubleClick = function () {
                isDeselecting = false;
                hasChanged = false;
            };

            // necessary due to a bug in AngularJS binding ng-model in the template
            Object.defineProperty(scope, 'selectedItem', {
                get: function () {
                    return scope.selectedItemInternal;
                },
                set: function (value) {
                    scope.selectedItemInternal = value;
                }
            });

            element.on('click', function () {

                $ionicModal.fromTemplateUrl('templates/radio-list.html', {
                    scope: scope
                }).then(function (m) {
                    modal = m;

                    // due to bug in ionic framework, scroll won't work unless we do this
                    ionic.keyboard.hide();

                    modal.show();
                });

            });

            element.on('$destroy', function () {
                if (modal) {
                    modal.remove();
                    modal = null;
                }
            });

        }
    };
})

这是我的收音机-list.html

<ion-modal-view>

    <div class="text-center" ng-show="header">
        <h5>{{header}}</h5>
    </div>

    <ion-content style="position: absolute; top: {{showCancel ? '30px': '0'}}; bottom: {{showCancel ? 103 : 53}}px; border: 1px grey;border-bottom-style: solid; width: 100%;">
        <div ng-repeat="item in items">
            <ion-radio ng-click="click($event)" ng-dblclick="doubleClick()" ng-model="selectedItem" ng-value="item">{{item.toString()}}</ion-radio>
        </div>
    </ion-content>

    <a class="button button-full button-energized" ng-show="showCancel"
       style="position: absolute; bottom: 50px; width: 100%; margin-top: 2px; margin-bottom: 2px;"
       ng-click="$event.stopPropagation();hide(false)">Cancel</a>

    <a class="button button-full button-energized"
       style="position: absolute; bottom: 0; width: 100%; margin-top: 2px; margin-bottom: 2px;"
       ng-click="$event.stopPropagation();hide(true)">{{doneText || 'Done'}}</a>

</ion-modal-view>

这是用法:

                <label class="item item-input validated" radio-list items="locations"
                       selected-item="account.locationPreference">
                    <span class="input-label">LOCATION</span>
                    <input type="hidden" ng-model="account.locationPreference" name="locationPreference"
                           required="required">
                    <span ng-show="account && !account.locationPreference"
                          class="placeholder value">Neighborhood</span>

                    <span class="input-value">{{account.locationPreference}}</span>
                </label>

阅读范围继承和 somewhat unintuitive behavior 设置值时产生的结果,就像 ng-model 所做的那样。

在这种情况下,ng-repeat 为每次迭代创建一个子作用域,因此当您有

ng-model="selectedItem"

您正在子作用域上创建 selectedItem 属性 并在其中设置值 - 而不是在指令的隔离作用域上。

作为快速修复,您可以直接设置 $parent.selectedItem

<div ng-repeat="item in items">
  <ion-radio ng-model="$parent.selectedItem" ng-value="item">{{item.toString()}}
  </ion-radio>
</div>

Demo 1

或者,您可以使用 bindToControllercontrollerAs 语法:

return {
  // ...
  scope: {
    // whatever you have
  },
  bindToController: true,
  controllerAs: vm,
  controller: angular.noop
}

并在模板中使用别名:

<div ng-repeat="item in vm.items">
  <ion-radio ng-model="vm.selectedItem" ng-value="item">{{item.toString()}}
  </ion-radio>
</div>

Demo 2