【问题标题】:How to use the last valid modelValue if a model becomes invalid?如果模型无效,如何使用最后一个有效的模型值?
【发布时间】:2015-12-14 05:49:25
【问题描述】:

我正在开发一个应用程序,当用户更改某些内容(例如输入字段的值)时会自动保存更改。我编写了一个autosave 指令,该指令被添加到所有应该自动触发保存事件的表单字段中。

模板:

   <input ng-model="fooCtrl.name" autosave>
   <input ng-model="fooCtrl.email" autosave>

指令:

  .directive('autosave', ['$parse', function  ($parse) {

    return {
      restrict: 'A',
      require: 'ngModel',
      link: function (scope, element, attrs, ngModel) {

        function saveIfModelChanged () {
          // save object containing name and email to server ...
        }

        ngModel.$viewChangeListeners.push(function () {
          saveIfModelChanged();
        });
      }
    };
  }]);

到目前为止,这一切对我来说都很好。但是,当我将验证添加到组合中时,例如将输入字段验证为有效的电子邮件地址,一旦 viewValue 更改为无效的电子邮件地址,modelValue 就会设置为 undefined

我想做的是:记住最后一个有效的 modelValue 并在自动保存时使用它。如果用户将电子邮件地址更改为无效,则包含nameemail 的对象仍应保存到服务器。使用当前有效的name 和最后一个有效的email

我从保存最后一个有效的 modelValue 开始,如下所示:

添加了验证的模板:

   <input type="email" ng-model="fooCtrl.name" autosave required>
   <input ng-model="fooCtrl.email" autosave required>

保存 lastModelValue 的指令:

  .directive('autosave', ['$parse', function  ($parse) {

    return {
      restrict: 'A',
      require: 'ngModel',
      link: function (scope, element, attrs, ngModel) {

        var lastModelValue;

        function saveIfModelChanged () {

          // remeber last valid modelValue
          if (ngModel.$valid) {
             lastModelValue = ngModel.$modelValue;
          }

          // save object containing current or last valid
          // name and email to server ...
        }

        ngModel.$viewChangeListeners.push(function () {
          saveIfModelChanged();
        });
      }
    };
  }]);

我的问题是,如何在保存时使用lastModelValue,但在视图中保留无效值?

编辑:

另一种可能性,as suggested by Jugnu below,是在验证器中包装和操作构建。

我尝试遵循:包装所有现有验证器并记住最后一个有效值,以在验证失败时恢复它:

Object.keys(ngModel.$validators).forEach(function(validatorName, index) {
    var validator = ngModel.$validators[validatorName];
    ngModel.$validators[validatorName] = createWrapper(validatorName, validator, ngModel);
});

function createWrapper(validatorName, validator, ngModel){

  var lastValid;

  return function (modelValue){

    var result = validator(modelValue);

    if(result) {
      lastValid = modelValue;
    }else{
        // what to do here? maybe asign the value like this:
      // $parse(attrs.ngModel).assign(scope, lastValid);
    }

    return result;
  };
}

但我也不确定如何继续使用这种方法。我可以在没有 AngularJS 的情况下设置模型值并尝试验证新设置的值吗?

【问题讨论】:

  • 鉴于您也在开发应用程序的后端,我会考虑将整个问题外包给后端。如果后端恰好是 node.js,我只需将这些值 v 更新到我的数据库中,v!==undefined 保存并为自己节省一些工作时间。我了解您可能无法这样做或出于学习目的而进行此项目,这只是我对开发效率的想法。

标签: angularjs angularjs-validation


【解决方案1】:

我创建了一个简单的指令,它作为 ng-model 指令的包装器,并将始终保持最新的有效模型值。它被称为 valid-ng-model,应该在您希望获得最新有效值的地方替换 ng-model 的使用。

我创建了一个示例用例here,希望你会喜欢。欢迎任何改进意见。

这是valid-ng-model指令的实现代码。

app.directive('validNgModel', function ($compile) {
  return {
      terminal: true,
      priority: 1000,
      scope: {
        validNgModel: '=validNgModel'
      },
      link: function link(scope, element, attrs) {

        // NOTE: add ngModel directive with custom model defined on the isolate scope
        scope.customNgModel = angular.copy(scope.validNgModel);
        element.attr('ng-model', 'customNgModel'); 
        element.removeAttr('valid-ng-model');

        // NOTE: recompile the element without this directive
        var compiledElement = $compile(element)(scope);
        var ngModelCtrl = compiledElement.controller('ngModel');

        // NOTE: Synchronizing (inner ngModel -> outside valid model)
        scope.$watch('customNgModel', function (newModelValue) {
          if (ngModelCtrl.$valid) {
            scope.validNgModel = newModelValue;
          }
        });

        // NOTE: Synchronizing (outside model -> inner ngModel)
        scope.$watch('validNgModel', function (newOutsideModelValue) {
          scope.customNgModel = newOutsideModelValue;
        });
      }
    };
});

编辑:没有隔离范围的指令实现:Plunker.

【讨论】:

  • 我玩过 plunker,它看起来很有前途!我将尝试将它集成到我们的应用程序中并返回结果。你认为合并autosavevalid-ng-model 指令会有问题/困难吗?
  • 谢谢,很高兴你喜欢它。对于合并这两个指令,这不是问题,但恕我直言,它们是关于我不喜欢混合的两个不同的问题。但是,如果您认为这样会更好,请随意合并它们,我也可以为您提供帮助。
  • 谢谢,但我想我可以设法合并指令。但是,我想知道另一个方面:您的指令创建了一个独立的范围,而 ngModel 和我的 autosave 指令都没有。如果没有孤立的范围,这可能吗?我们必须将customNgModel 分配给范围吗?
  • 由于这个孤立的范围,我实际上面临着不同的问题:1. 在我的自动保存指令中,我现在必须使用 scope.$parent.$eval(...);,而不是 scope.$eval(...);,所以我必须同时搜索当前范围和父范围取决于使用valid-ng-modelng-model 2. 将ng-model 更改为valid-ng-model 时,bootstrap-ui 的日期选择器弹出窗口停止工作。 3、ng-disabled="foo.enabled"在将ng-model="foo.title"更改为valid-ng-model="foo.title"时停止工作
  • 我的方法的基本思想是将 ng-model 指令绑定到某个内部模型,这样我就可以控制传递给我的外部(真实)模型的值,同时保持它的功能。这就是指令需要不需要隔离的新范围的原因。这是使用原型从其父级继承的新范围实现的指令。它应该可以解决您遇到的 #1 和 #3 问题,对于 #2 我不确定,您应该尝试。告诉我你的想法,如果它对你有用,我会更新我的答案。链接:plnkr.co/edit/3sCddqzHYy99XcIh8U5f?p=preview
【解决方案2】:

由于您要为每个字段修改发送整个对象,因此您必须在某处保留该整个对象的最后一个有效状态。我想到的用例:

  1. 你有一个有效的对象{ name: 'Valid', email: 'Valid' }
  2. 您将名称更改为无效;名称输入处的autosave 指令知道它自己的最后一个有效值,因此会发送正确的对象。
  3. 您也将电子邮件更改为无效。放置在电子邮件输入中的autosave 指令知道它自己的最后一个有效值,但不知道名称。如果最后一个已知的良好值没有集中,则会发送像 { name: 'inalid', email: 'Valid' } 这样的对象。

所以建议:

  1. 保留您正在编辑的对象的经过清理的副本。通过清理,我的意思是任何无效的初始值都应该替换为有效的原始值(例如零、空值等)。将该副本公开为控制器成员,例如fooCtrl.lastKnowngood
  2. autosave 知道最后一个已知的良好状态,例如如:

    <input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required />
    
  3. 在该对象中保留最后一个已知的良好局部值;利用ng-model 表达式,例如如:

    var lastKnownGoodExpr = $parse(attrs.autosave);
    var modelExpr = $parse(attrs.ngModel);
    
    function saveIfModelChanged () {
        var lastKnownGood = lastKnownGoodExpr(scope);
    
        if (ngModel.$valid) {
            // trick here; explanation later
            modelExpr.assign({fooCtrl: lastKnownGood}, ngModel.$modelValue);
        }
    
        // send the lastKnownGood object to the server!!!
    }
    
  4. 发送lastKnownGood 对象。

技巧、缺点以及如何改进: 将本地模型值设置为lastKnownGood 对象时,您使用的上下文对象与当前范围不同;该对象假定控制器被称为fooCtrl(参见modelExpr.assign({fooCtrl: lastKnownGood}, ...) 行)。如果您想要一个更通用的指令,您可能希望将根作为不同的属性传递,例如:

<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required
    autosave-fake-root="fooCtrl" />

您也可以自己对ng-model 表达式进行一些解析以确定第一个组件,例如子字符串 0 → 第一次出现点(再次简单化)。

另一个缺点是您如何处理更复杂的路径(在一般情况下),例如fooCtrl.persons[13].address['home'].street - 但这似乎不是你的用例。


顺便说一句:

ngModel.$viewChangeListeners.push(function () {
    saveIfModelChanged();
});

可以简化为:

ngModel.$viewChangeListeners.push(saveIfModelChanged);

【讨论】:

  • 谢谢,我喜欢这些建议!这让我想到:如果我开始摆弄模型表达式,我可以为原始对象创建一个代理。如果原始模型无效,我会为代理设置值并将代理对象发送到服务器。
  • 这似乎与这个想法很接近,并且如果我做对了也可以工作 - 一些例子会有所帮助。
【解决方案3】:

Angular 默认验证器只会在模型有效的电子邮件地址时为其赋值。要克服这个问题,您需要覆盖默认验证器。

更多参考请见:https://docs.angularjs.org/guide/forms#modifying-built-in-validators

您可以创建一个directive,将无效模型值分配给某个范围变量,然后您就可以使用它了。

我为电子邮件验证创建了一个小型演示,但您可以将其扩展为涵盖所有其他验证器。

这里是小提琴:http://plnkr.co/edit/EwuyRI5uGlrGfyGxOibl?p=preview

【讨论】:

  • 在验证器中包装和操作构建似乎是一个不错的选择。我试图沿着这条路走,但卡住了。你能看看我对这个问题的编辑吗?您还有什么建议吗?
  • 我不认为这是一个选项,覆盖验证器将意味着即使验证不成功,它们也需要返回验证成功以保留模型值。那会破坏它们的功能。此行取自 ngModelCtrl 的源代码,并在所有验证器运行后执行: ctrl.$modelValue = allValid ?模型值:未定义;
猜你喜欢
  • 1970-01-01
  • 2014-09-15
  • 1970-01-01
  • 2015-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-17
  • 2016-07-13
相关资源
最近更新 更多