javascript - 如何检测自定义指令内 ng-model 对象的更改?

标签 javascript angularjs angularjs-directive

考虑这个Plunker这具有我所追求的行为。

问题在于 1)我认为我做错了,2)它使用了深表,这很昂贵并且应该是不必要的。

这个例子有点做作。我的应用程序中实际上拥有的是一个功能齐全的预输入/自动完成控件,它绑定(bind)到对象并根据表达式呈现它们。 plunker 被精简以仅显示与该问题相关的部分; 如何正确实现这种事情?

主要困难是确保在输入中输入“某些内容”并单击按钮应该传播到指令中并导致 span.outlet进行相应更新。即,单击 Alpha 并尝试更改描述应该会导致页面上显示“Selected: A - Something”。

如果我删除深度监视,除非我用新的对象引用替换 $scope.selected 而不是仅仅更改现有对象引用上的属性(请参阅 punker 的 mainCtrl 中的注释),否则不会发生这种情况。

因此,第一个要求是自定义指令处理对象,而不仅仅是字符串。

第二个要求是该指令必须能够更新 span.outlet每当 $scope.selected对象从自定义指令外部更改。

第三,指令应该尽可能高效。这就是我提出这个问题的原因。 ng-model allready 内部有浅 $watch,现在我在其上添加了一个深 $watch,这对性能不利。有没有办法在没有如此深入的 $watch 的情况下做到这一点?

最后,如果我不必拥有scope.ngModel 绑定(bind),那就太好了。感觉很脏。

相关标记:

<input ng-model="newDescription">
<button ng-click="setNewDescription(newDescription)">Set description</button>
<hr/>
<my-list ng-model="selected" expression="{{::expression}}" options="options"></my-list>

相关主控代码:

  $scope.options = [
    {key:'A', desc: 'Alpha'},
    {key:'B', desc: 'Beta'},
    {key:'G', desc: 'Gamma'},
    {key:'D', desc: 'Delta'}
  ];
  $scope.selected = $scope.options[1];
  $scope.expression = '{{key}} - {{desc}}';

myList 指令:

app.directive('myList', ['$interpolate', function($interpolate) {
  return {
    restrict: 'E',
    require: 'ngModel',
    replace: true,
    template: '<div>Selected: <span class="outlet"></span><ul><li ng-repeat="item in vm.items"><a href="" ng-click="vm.select(item)">{{vm.render(item)}}</a></li></ul></div>',
    scope: {
      ngModel: '=',
      items: '=options'
    },
    link: Link,
    controller: Ctrl,
    controllerAs: 'vm',
    bindToController: true 
  };

  function Link(scope, element, attrs, ngModelCtrl) {
     var outlet = element.children()[0];
     scope.vm.render = $interpolate(attrs.expression);

     ngModelCtrl.$formatters.push(function(modelValue) {
       if (ngModelCtrl.$isEmpty(modelValue))
            return '';
        else
            return scope.vm.render(modelValue);
     });

     ngModelCtrl.$render = function() {
       console.log('rendering', ngModelCtrl.$viewValue);
       outlet.textContent = scope.vm.render(ngModelCtrl.$modelValue);
     };

    ngModelCtrl.$parsers.push(function(value) {
        // Only gets called due to the $setViewValue call in the deep $watch.
        // We don't have a way of going from a string to an object, but the $modelValue contains the right thing.
        console.log('parsing', value);
        return ngModelCtrl.$modelValue;
    });

    // I would prefer it if I could solve this without a deep watch on ngModel!
    scope.$watch('vm.ngModel', function(n, o) {
      if (angular.equals(n, o)) return;
      console.log('ngModel $watch\r\n', o, '->\r\n', n);
      ngModelCtrl.$setViewValue(scope.vm.render(n));
      ngModelCtrl.$render();
    }, true); 
  }

  function Ctrl($scope) {
    this.select = function(item) {
      console.log('selecting', item);
      this.ngModel = item;
    }.bind(this);
  }
}]);

非常感谢任何有关此问题的帮助,因为我已经尝试解决这个问题有一段时间了。谢谢!

最佳答案

我不确定我是否完全理解您的需求,但我准备了sample

app.directive('myList', ['$interpolate', function($interpolate) {
  return {
    restrict: 'E',
    replace: true,
    template: '<div>Selected: <span class="outlet"></span><ul><li ng-repeat="item in vm.items"><a href="" ng-click="vm.select(item)">{{vm.render(item)}}</a></li></ul></div>',
    scope: {
      selected: '=',
      items: '=options'
    },
    link: Link,
    controller: Ctrl,
    controllerAs: 'vm',
    bindToController: true 
  };

  function Link(scope, element, attrs, ctrl) {
     var outlet = element.children()[0];
     scope.vm.render = $interpolate(attrs.expression);

    scope.$watch(function () {
      return ctrl.selected;
    }, function (value) {
      console.log('rendering', value);
      outlet.textContent = scope.vm.render(value);
    });
  }

  function Ctrl($scope) {
    this.select = function(item) {
      console.log('selecting', item);
      this.selected = item;
    }.bind(this);
  }
}]);

HTML:

 <input ng-model="newDescription">
 <button ng-click="setNewDescription(newDescription)">Set description</button>
 <hr/>
 <my-list selected="selected" expression="{{::expression}}" options="options"></my-list>

更新2

Sample使用ng-model。您不需要在 $watch 中调用 $render 函数。模型自动更改后调用。

也是如何使用 ngModel here 创建指令的好例子.

关于javascript - 如何检测自定义指令内 ng-model 对象的更改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30617062/

相关文章:

angularjs - 注入(inject)动态 html 似乎无法编译

javascript - AngularJS:如何将常量对象绑定(bind)到指令

javascript - 通过 Angular 指令在 html 模板中迭代配置数组?

javascript - 使用 Node.js 访问 azure 表存储

javascript - 发布非形式值?

javascript - JQGrid : How to get Selected Row ID of Custom "Delete" column

javascript - 将指令属性添加到指令元素

javascript - 使用 jQuery 打开关闭按钮

javascript - 将两个额外的选项添加到带有 ngOptions 的选择列表中

javascript - TypeScript 构建错误 : Property does not exist on type 'IStateParamsService'