65.9K
CodeProject 正在变化。 阅读更多。
Home

Angular 教程 - 第 6 部分: 构建和验证数据录入表单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (19投票s)

2015 年 7 月 16 日

CPOL

11分钟阅读

viewsIcon

46879

downloadIcon

1448

如何使用适当的验证技术在 Angular 中构建数据录入表单。

引言

在本文中,我们将探讨如何在 Angular 中使用适当的验证技术构建数据录入表单。我们还将了解控制器应如何处理数据录入表单并将数据传播到各自的业务逻辑组件,即服务。

完整系列链接

  1. AngularJS 教程 - 第一部分:AngularJS 简介
  2. Angular 教程 - 第 2 部分: 理解模块和控制器
  3. Angular 教程 - 第 3 部分: 理解和使用指令
  4. Angular 教程 - 第 4 部分: 理解和实现过滤器
  5. Angular 教程 - 第 5 部分: 理解和实现服务
  6. Angular 教程 - 第 6 部分: 构建和验证数据录入表单
  7. Angular 教程 - 第 7 部分: 理解单页应用程序和 Angular 路由

背景

每当我们谈论数据录入表单时,首先想到的是如何在业务逻辑中处理用户数据以及如何确保在应用程序中传递正确的数据。在本文中,我们的主要重点将是探讨控制器应如何处理来自视图的数据,以及如何使用 Angular 的 ng-form 指令来创建更好的数据录入表单。

Using the Code

让我们从构建一个简单的表单开始讨论,该表单将接受 book 作为输入。稍后,我们可以使用相同的表单在系统中创建新的 book 实体,以及更新系统中现有的 book

理解 ngForm 指令

上图展示了实现表单所需的纯 HTML。我们使用 Bootstrap 来设置表单样式。在继续之前,我们首先需要了解当表单放入 Angular 应用程序中时,它将如何作为指令运行。实际上,如果我们将 form 标签放入 Angular 应用程序中,Angular 会自动将其默认表单替换为自己的表单指令。这样,只需将表单放在页面上,我们就可以使用 Angular 表单指令 (ngForm) 的所有功能。

了解此事实后,经常出现的一个问题是 Angular 为什么要这样做,并将其自己的表单指令而不是我们的纯 HTML 表单。这个问题的答案是,首先,纯 HTML 表单在提交时旨在对服务器进行全页面回传。我们可以使用自定义代码自己阻止这种情况,但 Angular 指令负责阻止此行为。但是,由于某些不明确的原因,如果我们仍然希望 Angular 表单执行全页面回传,也可以通过在表单中指定 action 属性来完成。但这对于大多数客户端应用程序来说可能不是必需的。

Angular 这样做的第二个原因是为我们提供大量可在应用程序中使用的功能。其中一个功能是 ngSubmitngClick 指令的可用性。这些指令可用于在表单提交时或在表单中单击提交按钮时连接我们的函数。另一个可能非常有用的功能是 Angular 为表单中包含的所有表单元素提供了各种状态。这些状态在提交表单之前验证表单时非常有用。我们将在本文的稍后部分探讨这些状态和表单提交。目前,可以说,每当我们将表单标签放在页面上时,我们实际上都在使用一个功能齐全且充满活力的 ngForm 指令。

更新服务

在我们创建表单以创建或更新 book 实体之前,让我们更新 localBookService 以便能够处理创建和编辑请求。以下代码显示了服务如何实现创建和编辑功能。

(function () {

    var localBooksService = function () {

        // Just a temporary variable to mimic auto increment on client side
        var nextIdForBook = 6;

        var _books = [
                { ID: 1, BookName: "4 Test Books", AuthorName: "5 Test Author", ISBN: "5 TEST" },
                { ID: 2, BookName: "5 Test Books", AuthorName: "4 Test Author", ISBN: "1 TEST" },
                { ID: 3, BookName: "1 Test Books", AuthorName: "3 Test Author", ISBN: "2 TEST" },
                { ID: 4, BookName: "2 Test Books", AuthorName: "2 Test Author", ISBN: "4 TEST" },
                { ID: 5, BookName: "3 Test Books", AuthorName: "1 Test Author", ISBN: "3 TEST" }
        ];

        var _addBook = function (book) {
            book.ID = nextIdForBook;
            nextIdForBook += 1;
            _books.push(book);
        }

        var _updateBook = function (book) {
           // A lame way of updating the book in the list
            for (var i = 0; i < _books.length; ++i) {
                if (_books[i].ID == book.ID) {
                    _books[i] = book;
                }
            }
        }

        return {
            books: _books,
            addBook: _addBook,
            updateBook: _updateBook
        };
    }

    angular.module('myAngularApplication').factory('localBooksService', [localBooksService]);
}());

控制器支持创建和编辑

接下来我们需要做的是让 $scope 对象跟踪从视图中添加和删除的项。我们还需要函数从视图中获取值并调用相应的服务函数。让我们看看控制器代码并尝试理解正在发生的事情。

(function () {

    var booksController = function ($scope, $filter, $window, localBooksService) {
        $scope.message = "List of books";

        $scope.books = [];
        $scope.book = {};
        $scope.editBook = null;

        $scope.fetchBooks = function () {
            $scope.books = localBooksService.books;
        }

        $scope.addBook = function () {
            localBooksService.addBook($scope.book);
            $scope.book = {}
            $scope.fetchBooks();
        }

        $scope.updateBook = function () {
            localBooksService.updateBook($scope.editBook);
            $scope.editBook = null;
            $scope.fetchBooks();
        }

        $scope.setEditBook = function (selected) {
            $scope.editBook = angular.copy(selected);
            $scope.book = {}
        }

        $scope.cancelEdit = function () {
            $scope.editBook = null;
            $scope.book = {}
        }

        $scope.fetchBooks();
    }

    angular.module('myAngularApplication').controller
    ('booksController', ["$scope", "$filter", "$window", "localBooksService",  booksController]);
}());

我们在控制器中做的是创建了一个 $scope.book 对象,它将跟踪从视图中添加的书籍。此对象将绑定到表单,当表单提交时,我们将调用 $scope.addBook,它反过来使用我们的 service 类将此书籍项保存到列表中。

为了能够更新 book 实体,首先我们需要一个对象来跟踪用户正在更新哪本书,因此我们创建了 $scope.editBook。然后,我们让用户可以选择他想更新哪本书以及如果他取消更新应该怎么做。因此,我们创建了 $scope.setEditBook,它将使用用户选择的书籍作为 $scope.editBook。另一方面,取消将把 $scope.editBook 重置为 null

如果我们仔细查看 $scope.setEditBook,我们可以看到我们正在使用 angular.copy 方法。这样做的原因是,我们不希望脏值进入我们的 book 列表,因此如果用户选择了原始对象,我们会创建一个副本。当用户选择更新时,我们将使用此副本对象更新实际列表。这种方法使无效更改不会传播到实际模型变得更容易。由于我们正在处理一个 copy 对象,如果值无效,那么我们可以选择忽略更新或让用户知道它们无效需要修复。

注意:对于编辑和更新场景,建议为编辑操作使用对象副本,因为它减少了无效值传播到服务器的可能性。

现在我们有了执行实际操作所需的所有代码,让我们回到手头的主题并创建我们的表单。

创建图书实体

让我们使用我们前面看到的相同表单来创建 book 实体。我们需要做的第一件事是将所有 $scope.book 属性与各自的 UI 元素绑定。此外,submit 需要与 addBooks 函数连接。让我们看看这会是什么样子。

现在运行应用程序,它将看起来像这样

现在我们已经把所有东西都连接起来了,如果我们点击创建按钮,我们应该能够将这本书添加到我们的列表中。

注意:这里有一个小问题,无效的 book 实体也会进入列表。即使我们只是点击创建而没有输入任何内容,也会创建一个条目。如何修复它,我们将在下一节稍后讨论。

编辑实体

现在控制器和服务中所有编辑功能都已就绪,也许我们应该着手创建编辑表单。我们需要做的第一件事是为每个 book 实体设置一个编辑按钮,以便单击此按钮将指示用户要更新哪个实体。我们可以通过调用 $scope.setEditBook 函数来做到这一点。因此,让我们在 ng-repeat 块中执行此操作。

现在,无论何时用户点击此按钮,所选行的实体都将被设置为用户想要编辑的 book。现在让我们创建一个用于编辑的表单。此表单将类似于我们为创建而创建的表单,因为它应该绑定到 $scope.editBook 并且提交应该调用 updateBook 函数。但我们需要在这里多做一些事情。我们需要做的第一件事是跟踪 ID。由于我们在这里处理实际模型的副本,因此我们将 ID 放在此表单的隐藏字段中。我们需要做的第二件事是有一个取消按钮,它将重置 editBook 对象并有效地重置更新请求。

现在当我们运行这个应用程序并选择一个项目进行编辑时,我们可以看到它被选为要编辑的项目。

现在,如果我们将 ISBN 值更新为 1234 并点击更新,我们可以看到更改在列表中生效。

现在我们已经准备好创建和更新的表单。编辑仍然没有任何验证,但我们将在下一节中查看验证。

注意:在示例应用程序中,默认情况下,创建表单是可见的。当用户选择要编辑的项时,创建表单将消失,编辑表单将变为可见。我们可以将两者用于创建和编辑,但故意不这样做是为了避免复杂性并使代码免于跑题。

理解 Angular 验证框架

当前的实现存在一个主要问题,即没有进行任何验证。Angular 提供了相当多的功能,使验证变得非常容易。此外,它与 HTML5 验证属性协同工作,这也是一件好事。为了说明这种验证的工作原理,让我们设置一个非常简单的验证规则,即我们应用程序中的所有字段都是必需的。让我们看看如何在我们的应用程序中实现这一点。

我们需要做的第一件事是让应用程序知道我们正在掌控验证,并且我们不希望使用默认的 HTML5 验证。这可以通过在 form 标签上设置 novalidate 属性来完成。此外,为了能够使用验证,我们需要为 form 设置一个 name。Angular 使用这个 name 让我们知道表单控件的状态。

完成这个小改动后,我们就可以进行验证了。首先要实现必需验证,让我们为所有输入添加 required 属性。

现在让我们开始看看 Angular 的魔力。Angular 有一些与 ng-form 中的所有输入元素相关的属性。以下是其中一些属性

  • $pristine
  • $dirty
  • $valid
  • $invalid

如果输入值自页面加载以来没有更改,则 $pristinetrue$dirty 则恰好相反。当值已更改时,它为 true。如果输入包含有效值,则 $validtrue,而 $invalid 则恰好相反,即如果输入元素包含无效值,则它为 true

现在我们如何使用这些属性进行验证。我们将做的是,每当输入包含无效值时,我们将不让用户提交表单。我们可以通过将表单对象传递到 addBooks 函数中并检查它是否有效来完成此操作。

$scope.addBook = function (frmAddBook) {
    if (frmAddBook.$valid) {
        localBooksService.addBook($scope.book);
        $scope.book = {}
        $scope.fetchBooks();
    }
    else {
        alert("Invalid values. All values are required");
    }
}

现在这将阻止无效的图书条目被保存。但如果我们在编辑页面时能立即获得反馈,那也会很好。因此,让我们尝试使用这些属性对单个元素应用一些 CSS 类。为了能够在单个元素上使用这些属性,每个输入元素都必须有一个 name 并且应该使用 ng-model 进行绑定。所以让我们尝试为 book name 执行此操作。

我们在这里做的是,我们检查输入字段是否是 dirty,即用户是否已开始更改值,如果它是 invalid,我们将一个 CSS 类附加到元素。如果输入字段无效并且已被触摸,此 CSS 类简单地显示一个红色边框。

因此,使用上面所示的这些属性,我们可以通过多种不同的方式在表单上执行验证。也许一个想法是,如果表单值无效,则禁用提交按钮本身。对于单个属性,我们也可以使用这些属性实现许多基于 CSS 的反馈机制。

注意:我没有为所有字段实现这种基于 CSS 的反馈。它仅在一个字段上完成,用于演示目的。添加和编辑的验证已在控制器中完成,以避免列表中出现无效值。

关注点

在本文中,我们讨论了 Angular 数据录入表单的基础知识。我们还探讨了如何使用 Angular 在 form input 元素上提供的属性执行验证。我们所看到的一切都只是冰山一角,因为在构建数据录入表单时有许多最佳实践可用。我们也没有研究 Angular UI,它是另一个可用于使用 Bootstrap 组件简化表单创建的插件。本文的主要目的是让读者熟悉 ngForm 指令以及如何使用 Angular 执行数据录入和验证。本文是从初学者的角度编写的。我希望这能提供信息。

历史

  • 2015 年 7 月 16 日:第一个版本
© . All rights reserved.