【问题标题】:How to use angularjs form validation in a non-angular SPA app如何在非 Angular SPA 应用程序中使用 angularjs 表单验证
【发布时间】:2014-04-25 16:18:36
【问题描述】:

我想知道是否有人可以提供帮助。从表面上看,我的问题标题可能听起来有点愚蠢!我正在尝试在非 angularjs 应用程序中使用 angularjs 表单验证!让我试着解释一下……

我们有一个“传统”网络应用程序。它不是SPA。后端是java,java应用服务器管理session和里面的数据。每个页面都是一个完整的 http 请求/响应。 (有少量 ajax 请求/响应,但这些是为页面添加一些亮点,而不是它的核心功能)。在这方面,应用程序的架构非常传统/老式,因为服务器端 java 代码负责生成标记并从其会话存储中保存的模型数据版本填充表单字段值。 (我认为这是问题的症结所在)

该应用程序主要是基于 HTML 表单的应用程序,为了增强用户体验,我们编写了一些基于 jQuery 的 javascript 字段验证器。由于多种原因,这些已经开始有点失控,我们正在探索替代方案。

一个简单的 google 搜索会找到无数用于表单验证的 jQuery 插件。我们正在研究这些,但随机的 jQuery 插件不是我们喜欢的方法(我们倾向于避开 'somebloke.com' 插件,因为我们无法保证它们的编写、浏览器兼容性、未来维护、如何好吧,它们可以与其他插件等配合使用——我们以前曾被这种东西烫伤过手指)

所以我们正在寻找其他方法,目前正在探索使用 angularjs。
能够在我们的架构中“温和地引入”angularjs 有一些优势。它来自一个稳定的同类最佳组织(即它不是“somebloke.com”),因此得到了很好的支持和维护。它鼓励我们以非常模块化和可测试的方式编写我们的 js(我们当前的 jQuery 插件、回调等老鼠巢远非如此!)。 angularjs 表单验证基于 html5 标准,具有声明性和语义性。它为我们提供了一种将其他基于 jQuery 的代码迁移到更好的东西(angularjs 指令)的方法。总的来说,如果我们可以将 angularjs 分层到我们当前的应用程序中,它将为我们在未来某个时候将应用程序转换为现代 SPA 提供良好的基础。

此时将整个应用程序重写为 angularjs(或任何其他 mv* 框架)SPA 不是一种选择,因此如上所述,我们正在考虑一次引入少量功能;而今天的挑战是表单验证。

所以,这就是背景。

我已经剥离了我们当前的 js 客户端验证,而我们的服务器端 java 代码正在生成如下标记:

<form method="POST" action="/renew">
    <input name="firstname" type="text" value="alf" />
    <input name="surname" type="text" value="garnet" />
    <input name="age" type="number" value="88" />
    <input type="submit" />
</form>

(输入字段的值已从服务器持有的模型填充到服务器端)

我已将 angularjs 库添加到页面中,并且表单验证工作如下:

<form method="POST" action="/renew" novalidate name="renewForm" 
      ng-controller="yourDetails" ng-submit="submitForm(renewForm, $event)">
    <input name="firstname" type="text" value="alf" required ng-model="firstname"/>
    <input name="surname" type="text" value="garnet" required ng-model="surname"/>
    <input name="age" type="number" value="88" required ng-model="age"/>
    <input type="submit" />
</form>

app.controller('yourDetails', function($scope) {
    $scope.submitForm = function(form, $event) {
        if (!form.$valid) {
            $event.preventDefault();
            return false;
        }
    };
});

这是一个合理的起点。从广义上讲,它的工作原理是 angularjs 正在处理表单验证和提交。执行submitForm 方法,如果表单无效,则进入if 块并取消表单提交。从这里我可以看到使用ng-show 等在字段中添加错误消息等很容易。

但问题是在每个 html 字段上使用ng-model。据我了解,我需要使用它,以便 angularjs 将字段绑定到表单,因此可以跟踪每个字段的有效状态。
但是,ng-model 似乎也设置了 2-way 数据绑定并将字段的值设置为其模型数据的版本......这是空的。例如:

我们的服务器端模板可能包含以下内容:
&lt;input th:field="*{firstname}" type="text" required ng-model="firstname"/&gt;

可能会生成此标记:
&lt;input name="firstname" value="alf" type="text" required ng-model="firstname"/&gt;

提供给客户端的标记包括value="alf"
但随后 angularjs 介入并为该字段设置了 2 向绑定。因为我们在 angularjs 范围内没有 firstname 属性,所以它用一个空白值初始化一个,并在字段的 DOM 中设置该空白值。
这会导致页面被浏览器渲染为字段中的空白值,即使在服务器端我们在模型中有值,并且服务器已经正确生成了标记等。

所以,我想我了解核心问题以及它发生的原因。我的问题是,我可以在每个字段上没有 ng-model 属性的情况下进行 angularjs 表单验证,还是有一个版本的 ng-model 指令只进行单向绑定 - 特别是 DOM -> 模型

任何帮助将不胜感激;

谢谢
内森

【问题讨论】:

    标签: angularjs validation angularjs-scope


    【解决方案1】:

    在服务器端生成表单时,可以使用 ng-init 初始化模型:

    <input ng-init="firstname='alf'" th:field="*{firstname}" type="text" required ng-model="firstname" />
    

    【讨论】:

    • 感谢您的回答,尽管我无法让 ng-init 按建议工作。我已经在上面的输入元素以及定义控制器的表单元素上尝试过它(因此我认为在哪里定义了范围?),但都不起作用。角度文档表明它仅对 ng-repeat 有效? docs.angularjs.org/api/ng/directive/ngInit
    【解决方案2】:

    好的,@Alexandre 的回答几乎是正确的,正是他的回答为我指明了最终解决方案的方向(所以他真的应该为此获得赞誉:))

    ng-init does 按照@Alexandre 的建议工作。我无法让它工作的原因是我试图在数字字段上使用它。

    以下是可行的,因为使用 ngInit 在模型上设置的值和 html 输入类型都是文本/字符串:

    <input ng-init="firstname='alf'" th:field="*{firstname}" type="text" required ng-model="firstname" />
    

    我在年龄字段上尝试如下:

    <input ng-init="age='88'" th:field="*{age}" type="number" required ng-model="age" />
    

    这不起作用,因为年龄属性在模型上设置为字符串,但 html 输入类型是数字。以下确实有效:

    <input ng-init="age=88" th:field="*{age}" type="number" required ng-model="age" />
    

    这让我意识到在 Angular 模型上设置的值需要匹配 html 输入类型的数据类型(chrome 肯定如此,不确定其他浏览器)(即模型中的字符串属性 - 甚至如果它们可以解析为数字 - 不能在带有 ngModel 的 html 数字字段中使用)

    考虑到这一点,我决定有两种选择。我可以在生成标记的代码中在服务器端执行此操作:

        <input ng-init="age=(some-potentially-complex-logic-to-workout-whether-its-a-string-or-number)" th:field="*{age}" type="number" required ng-model="age" />
    

    或者我可以使用自定义指令在客户端执行此操作。最后我选择了一个自定义指令,因为a)这意味着我可以尝试编写一个指令(学习的所有部分:))和b)我意识到可能还有其他需要特别考虑的情况可能会做它在服务器端更加复杂(即选择字段没有值,它们有一个选定的索引,您需要获取它的值;单选按钮都有一个值,但您只想设置选中的值收音机)

    这是我想出的:

    angularApp.directive('lvInitializeValueOnScope', function() {
        return {
            restrict: 'A',
            link: function(scope, element, attrs, controller) {
                var propertyName = attrs.name,
                    propertyValue = attrs.value,
                    elementName = element.get(0).tagName.toLowerCase(),
                    fieldType = ( elementName === 'input' ? attrs.type.toLowerCase() : elementName ),
                    // set expression to assume propertyValue is a string value
                    expression = propertyName + '=\'' + ( !!propertyValue ? propertyValue : '' ) + '\'';
    
                // if the input field type is number and propertyValue is parse-able as a number
                if (fieldType === 'number' && !isNaN(parseFloat(propertyValue))) {
                    // set expression without quotes surrounding propertyValue
                    expression = propertyName + '=' + propertyValue;
                }
                // if the field is a html select element
                if (fieldType === 'select') {
                    // propertyValue will be blank because select elements don't have a value attribute
                    // instead, we need to use the value of the child option element that is selected
                    propertyValue = $(element.html()).filter(":selected").val();
                    // set expression to assume propertyValue is a string value
                    expression = propertyName + '=\'' + ( !!propertyValue ? propertyValue : '' ) + '\'';
                }
                // if the input field type is a radio button but its not checked (selected)
                if (fieldType === 'radio' && !element.is(':checked')) {
                    // we need to reset the expression so a blank value is used
                    // doing this means that only the checked/selected radio button values get set on the model
                    expression = propertyName + '=\'\'';
                }
    
                // evaluate the expression, just as angular's ngInit does
                scope.$eval(expression);
            }
        };
    });
    

    (它看起来比实际更复杂,因为我把 cmets 留在了里面 - 把它们去掉,真的什么都没有)

    要使用它,我使用 ng-model 的每个字段,我还需要使用属性 data-lv-initialize-value-on-scope。您不需要在属性上传递任何值,指令从元素和 attr 参数中获取所需的一切

    到目前为止,它支持文本、数字、广播和选择字段。我正在处理的页面没有任何复选框,但是当我遇到这些页面时,我敢说我需要为它们添加一些代码。

    它有效,我认为我更喜欢使用带有负载的服务器端的 ng-init 来确定是否设置一个值、它的数据类型应该是什么等

    目前我能看到的唯一缺点是处理单选按钮集可能效率低下。 IE。如果你有 10 个单选按钮,每个按钮都标有 ng-model 和 data-lv-initialize-value-on-scope;所有 10 个都将运行 data-lv-initialize-value-on-scope 指令并在范围上设置一个值。至少 9 次迭代会在作用域上设置空白值,最多只有 1 次会在作用域上设置实际值。

    嘿嗬,它现在似乎工作:)。希望这对将来的某人有所帮助

    内森

    【讨论】:

      猜你喜欢
      • 2018-03-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-14
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多