AngularJS: 在解决承诺后更新表单字段的代码属于哪里?

AngularJS: Where does code belong that updates a form field after a promise is resolved?

在我的 AngularJS 应用程序中,我有一个包含 <textarea> 和提交按钮的模板。 <div>(可以很容易地成为 <form>)包装这两个元素并具有控制器。单击提交按钮时,控制器使用 $resource 服务将 <textarea> 中的值 POST 转换为 REST API。

模板:

<div ng-controller="MyController as vm">
  <textarea ng-model="vm.text" rows="5"></textarea>
  <button ng-click="vm.save()">Save</button>
</div>

控制器:

angular.module('myApp')
  .controller('MyController', MyController);

MyController.$inject = ['myRestApiResource'];

function MyController(myRestApiResource) {
  var vm = this;
  vm.save = function() {
    var params = [];
    var postData = {text: vm.text};
    myRestApiResource.save(params, postData)
      .then(function(res) { /* success handler */ })
      .catch(function(res) { /* error handler */ });
  };
}

当资源 save() 方法的 $promise 被拒绝时,响应数据(在某些情况下)包含一个 error.position 属性 引用字符串索引,其中vm.text.

中检测到语法错误

在那些情况下,我关注 <textarea> 元素和从 error.position 到下一个非单词字符的 set the selection range。我有一个功能,一切正常。

我的问题是:

是否应该在<textarea>(承诺被拒绝后)上执行设置选择范围的函数调用...

  1. 控制器
  2. 添加到文本区域的自定义指令
  3. 其他地方

...为什么?

这是一个关于关注点分离的问题。

控制器似乎是最明智的地方,但我的理解是控制器不应该执行 DOM 操作......这需要使用 $element 服务(或 jQuery) 就是为了这样做。

自定义指令需要订阅控制器广播的事件,然后设置自己的焦点和选择范围。这分开了关注点,但根据 Angular Best Practices $broadcast$on 应该只用于 "events that are relevant globally across the entire app"。

我想控制器和指令可以通过另一个服务进行通信,但这似乎是过度设计——这是我臭名昭著的,并且在这里努力避免! :-)

提前致谢!


解决方案

为了完整起见,这是我根据@kristin-fritsch 的回答编写的自定义指令:

指令

angular.module('myApp')
  .directive('selectionRange', selectionRangeDirective);

function selectionRangeDirective() {
  return {
    restrict: 'A',
    scope: {
      selectionRange: '='
    },
    link: function(scope, iElement) {
      var element = iElement[0];
      if (element.setSelectionRange && typeof element.setSelectionRange === 'function') {
        scope.$watch('selectionRange', function(range) {
          if (range && typeof range.start === 'number' && range.start > -1 && range.start < iElement.val().length) {
            element.focus();
            element.setSelectionRange(range.start, range.end);
          }
        });
      }
    }
  };
}

为了实现它,我将指令属性添加到我的文本区域:

<textarea ng-model="vm.text" rows="5" selection-range="vm.selection"></textarea>

...并在我的控制器中将 vm.selection 的值设置为具有 startend 属性的对象,当承诺被拒绝时。

myRestApiResource.save(params, postData)
  .then(function(res) { /* success handler */ })
  .catch(function(res) {
    var error = res.data.error;
    if (error && error.position) {
      var start = error.position;
      var end = start + vm.text.substr(start).search(/(\W|$)/);
      vm.selection = {start: start, end: end};
    }
  });

我建议你在文本区域添加一个指令。

<div ng-controller="MyController as vm">
  <textarea ng-model="vm.text" data-error-position="errorPosition" rows="5"></textarea>
  <button ng-click="vm.save()">Save</button>
</div>

然后你可以像这样设置 $promise 被拒绝时的位置

$scope.errorPosition = 50;

并在 errorPosition - 指令中处理 DOM 操作。