【问题标题】:How to update a form controller in AngularJS after changing input constraints更改输入约束后如何在AngularJS中更新表单控制器
【发布时间】:2014-08-26 13:03:42
【问题描述】:

我最近一直致力于动态更改表单上输入字段的约束。目的是制作一个后端驱动的表单,而所有字段及其约束都由服务器发送给我们。虽然我设法通过创建一个简单的指令来随意添加/删除约束,但似乎表单控制器没有接收这些更改,因此表单始终为 $valid。看看this jsfiddle,这里是指令:

myApp.directive('uiConstraints', [function(){

function applyConstraints(element, newVal, oldVal){
    //remove old constraints
    if(oldVal !== undefined && oldVal !== null){
        for (var i = 0; i < oldVal.length; i++) {
            element.removeAttr(oldVal[i].key);
        }
    }

    //apply new constraints
    if(newVal !== undefined && newVal !== null){
        for (var i = 0; i < newVal.length; i++) {
            var constraint = newVal[i];
            element.attr(constraint.key, constraint.value);
        }
    }
}

function link(scope, element, attrs){
    scope.$watch(attrs.uiConstraints, function(newVal, oldVal){
        applyConstraints(element, newVal, oldVal);
    });
}

return {
    restrict : 'A',
    link : link
};

}]);

所需的行为是这样的,就像在official angularjs plunker 上一样。但是,似乎在指令填充输入字段上的约束之前创建了 FormController,并且更新这些约束不会更新 FormController 中的相应值。

是否有人知道我是否可以强制 FormController 拾取指令对约束所做的更改?如果是这样,怎么办?我什至不知道从哪里开始......谢谢。

-- 编辑--
我无法让 plunker 工作(向其他人展示我的最新更改)所以这是我所拥有的 jsfiddle:latest
要更详细地描述该问题:

  1. 转到描述的 jsfiddle
  2. 如果您从文本框中删除初始值,它将变为红色(无效),但控制器不会拾取该值并仍会显示:

myform.$valid = true
myform.myfield.$valid = true

-- 编辑--
赏金说明无法识别 Stack Overflow 格式(例如 2 个空格用于换行等),因此这里的格式更易读:

由于这仍然是一个未解决且有趣的问题,我决定开始赏金。
要求是:
- 适用于古老的 AngularJS(1.0.3) 和更新版本(如果不能在 1.0.3 上完成,但有人在较新版本的 Angular 上完成,我将奖励赏金)
- 最初,一个字段对其没有限制(不是必需的,未设置最大值和最小值等)
- 在任何时候,该字段的约束都可以改变(它可能成为必需的,或者设置了值的模式等),并且可以删除任何现有的约束
- 所有约束都存储在对象或数组的控制器中
- FormController 获取更改,以便在约束时适当更改任何 $scope.FormName.$valid 在该表单中的任何字段上更改

我的 jsfiddle 是一个很好的起点。
感谢您的宝贵时间,祝您好运!

【问题讨论】:

  • 我花了一些时间研究这个问题,因为这是一个非常有趣的问题。基本问题是&lt;input&gt; 元素需要用它的新属性重新编译。使用 $compile 服务来执行此操作。我有一个半工作的笨蛋,但它没有完全发挥作用,所以我没有把它作为答案。 plnkr.co/edit/WlSyZQ6hPGA9B4oWRG43?p=preview

标签: angularjs angularjs-directive angularjs-controller


【解决方案1】:

看看这个PLUNK

.directive('uiConstraints', ["$compile",
function($compile) {
  function applyConstraints(element, newVal, oldVal) {
    //apply new constraints
    if (newVal !== undefined && newVal !== null) {
      for (var i = 0; i < newVal.length; i++) {
        var constraint = newVal[i];
        element.attr(constraint.key, constraint.value);
      }
    }
  }

  return {
    restrict: 'A',
    terminal: true,
    priority: 1000,
    require: "^form",
    link: function(scope, element, attrs, formController) {
      var templateElement;
      var previousTemplate;
      templateElement = element.clone(); //get the template element and store it 
      templateElement.removeAttr("ui-constraints");// remove the directive so that the next compile does not run this directive again
      previousTemplate = element;

      scope.$watch(attrs.uiConstraints, function(newVal, oldVal) {
        var clonedTemplate = templateElement.clone();
        applyConstraints(clonedTemplate, newVal);
        clonedTemplate.insertBefore(previousTemplate);

        var control = formController[previousTemplate.attr("name")];
        if (control){
           formController.$removeControl(control);
        }
        if (previousTemplate) {
          previousTemplate.remove();
        }

        $compile(clonedTemplate)(scope);
        previousTemplate = clonedTemplate;
      });
    }
  };
}]);

这里的想法是设置terminal: truepriority: 1000 让我们的指令首先被编译并跳过同一元素上的所有其他指令以获得模板元素。如果您需要了解更多信息,请查看我的答案:Add directives from directive in AngularJS

在获得模板元素之后,我删除了ui-constraints 指令以避免该指令被一次又一次地编译,这会在摘要循环中添加一个 $watch每次我们切换约束时。

每当约束发生变化时,我都会使用此模板元素构建一个包含所有约束的新元素,而无需 ui-constraints 指令并对其进行编译。然后我从 DOM 中删除前一个元素,并从表单控制器中删除它的控制器,以避免表单控制器中存在前一个元素的控制器引起的泄漏和问题。

【讨论】:

  • 我发现您的回答非常有见地和有用。不过,在这里,我不得不问:这是不是矫枉过正?我不喜欢重新编译模板或更改元素的想法——这感觉太像 DOM 操作了。我更喜欢创建具有我想要的行为的指令。这不是批评,而是更多的问题。
  • @Dave A:因为约束是动态的,我想不出除了动态编译元素之外的其他解决方案。
  • 感谢您的回答,以及解决我的问题,我从中学到了很多。我猜赏金是给你的。再次感谢:)
【解决方案2】:

对于初学者,AngularJS 有 ng-required 指令,允许您切换所需的验证

<input type="text" data-ng-required="isRequired" 
               data-ng-model="mydata" name="myfield" />

看小提琴:

Fiddle

使最小和最大逻辑动态化有点复杂。您当前的方法 JQuery - 类似于改变元素,这不是 AngularJS 强度。理想的答案是像ng-minlnegth 这样更智能的指令。

我会调查的。

【讨论】:

    【解决方案3】:

    快速而肮脏的解决方案:

    scope.$watch(attrs.uiConstraints, function(newVal, oldVal){
        applyConstraints(element, newVal, oldVal);
        if (newVal != oldVal) {
            element = $compile(element)(scope);
        }            
    });
    

    我不喜欢解决方案,而且它并不完美。这是一个有趣的问题,所以我仍在努力寻找更优雅的解决方案。

    【讨论】:

    • 这几乎是我想出的。但是,我发现它会以一种方式工作(从非要求到要求),但是当切换回来时,它将保持在要求状态。想不出来那个此外,它不会改变 form.input.$valid 等值。
    • 正如你们俩所说,它可以部分工作。 jsfiddle.net/eL3ce3oq/4 我必须第一次切换才能使 $scope.myform.$valid 拾取,然后它可以双向拾取。 $scope.myform.myfield.$valid 根本没有接...
    • 当我们一次又一次地编译元素时,这会导致问题,因为它编译了已经编译的元素。每次切换约束时,它都会在摘要中添加一个 $watch,...如果我们多次切换约束,这可能会导致计算机挂起。
    【解决方案4】:

    这是example

    你的问题是你试图在你的应用程序中使用范围,而你看错了。

    我改变了你的链接功能:

     link : function(scope, element, attrs){
                scope.$watch(attrs.constraints, function(newConstraints, oldConstraints){
                  applyConstraints(newConstraints, oldConstraints, element);
                }, true);
            }
    

    您的切换功能现在可以执行一些随机属性和内容:

    $scope.toggleConstraints = function(myconstraints){
        var getRandomPropOrValueName = function(){
          return Math.random().toString(36).substring(7);
        }
        $scope.myconstraints = [{key: getRandomPropOrValueName(), value: getRandomPropOrValueName() }];
      };
    

    【讨论】:

    • 事实上,我意识到 plunker 出于某种原因将人们重定向到旧版本的东西。我不知道为什么,所以我搬到了 jsfiddle。很抱歉将您的时间浪费在较旧的已解决问题上。我用 jsfiddle 更新了我原来的帖子。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 2023-03-12
    • 2017-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多