可重用的 AngularJS 组件:警告条
使用 AngularJS 构建可重用组件
引言
AngularJS 令人称道的一个方面是指令。指令可以创建为自定义组件,放置在 HTML 页面上。根据 AngularJS 文档,指令是 DOM 元素标记。当 AngularJS 应用程序运行时,它会编译这些标记,创建作为标记子元素的附加 DOM 元素,并为其附加动态行为。这是对指令基本概念的介绍。
让我们看一个例子
<select class="form-control" ng-model="vm.selectedValue"
ng-options="item as item.text for item of vm.optionValues">
</select>
这是一个简单的下拉列表。其中包含两个指令:一个是 ng-model
,也可以称为 ngModel
。这是最常见的指令,它将视图模型属性绑定到 UI 元素。另一个是 ng-options
或 ngOptions
,这是另一个指令。它遍历数据模型,在本例中是项目列表,并在下拉控件中显示这些项目。这两种都是基于属性的指令。在本教程中,我将创建一个简单的指令来演示如何创建典型指令。它将是一个基于元素或属性的指令。
示例项目概述
示例项目只有一个页面,包含一个用户注册表单。当用户在此页面上输入数据但缺少某些信息时,顶部会显示一个警告条。要关闭此警告条,用户可以单击其中的任何位置,它就会消失。当输入所有必填信息后,用户单击“提交”按钮,警告显示将替换为成功消息。
这是未输入用户数据的页面截图
这是显示错误的页面截图
成功提交用户数据后,这是显示成功消息的页面截图
我希望在本教程中共享的实际组件是这个警告条。它是可以放置在 Web 应用程序任何位置的那些小 HTML 片段之一。此类组件的代码可以到处复制,而且大多数情况下,更改一个组件不必在其他地方复制。但我之前遇到了代码重复。每当有机会创建可重用的东西时,我都会尝试创建它。本教程将展示如何创建这个小警告条以及如何使用它。
示例项目基于 Spring Boot。项目中的 Java 部分仅用于托管静态内容——显示用户注册的页面,以及 JavaScript 文件和其他页面文件。我不会展示 Java 部分的任何代码。如果您有兴趣,请有时间时自行查看。
接下来,我将开始讨论指令的设计,这完全是 JavaScript 和 HTML。
警告条指令的设计
我称此指令为警告条,是因为在大多数情况下,此指令会显示错误或警告。当它显示警告或错误时,背景颜色将是红色。有时,它也可用于显示成功消息。当它显示成功消息时,背景颜色将是令人愉悦的绿色。是的。此指令设计用于同时显示这两种类型的消息。
我想向您展示该指令在主应用程序中的使用方式。然后我再展示指令的定义方式。在 index.html 文件中,您会发现类似这样的内容
<div info-display op-success="vm.opSuccess" msg-to-show="vm.statusMessage" ></div>
这里,我添加了一个 div
,其中一个属性是 info-display
。这个 info-display
就是指令。当应用程序运行时,AngularJS 将编译并将此 div
与其下方的更多 HTML 标记链接起来。
请注意,指令属性使用 "info-display"
。这是 AngularJS 的约定。指令实际名称是 infoDisplay
。当您实际使用它时,名称全部小写,单词之间用连字符(或撇号)连接。这就是 AngularJS 如何从标记定位到指令定义的方式。具体如何做到这一点我不太确定,但我知道最终结果。这是显示警报条的标记
<div class="row ng-scope" ng-if="msgToShow != null && msgToShow.length > 0">
<div class="col-xs-12">
<div class="alert info-display ng-binding alert-success"
ng-class="{ 'alert-success': opSuccess, 'alert-danger': !opSuccess }"
ng-click="info.clickOnStatus()">
User registration request has been processed successfully.</div>
</div>
</div>
显然,这里发生了一些运行时 HTML 注入。这意味着我必须定义注入组件的 HTML 标记。在名为 static/assets/app/pages/infoDisplay.html 的文件中,您可以找到此类注入的标记。这是
<div class="row" ng-if="msgToShow != null && msgToShow.length > 0">
<div class="col-xs-12">
<div class="alert info-display"
ng-class="{ 'alert-success': opSuccess, 'alert-danger': !opSuccess }"
ng-click="info.clickOnStatus()">{{msgToShow}}</div>
</div>
</div>
这块标记非常简单,但有几点值得讨论。首先是 ngIf
或 ng-if
指令。它的用法如下
<div class="row" ng-if="msgToShow != null && msgToShow.length > 0">
...
</div>
在此例中,ngIf
用于确定此 row div
及其子元素何时显示。条件是当警告/错误/成功消息不为 null
且其 length
大于 0
时,它将被显示。ngIf
的妙处在于,当条件为 false
时,元素不会添加到 HTML DOM 树中。当您只希望在适当的时候添加 DOM 元素,并且在不适当的时候不让元素存在于 DOM 树中时,这是理想的选择。
我还要展示的是 ngClass
或 ng-class
指令的用法。这是一个有用的指令。它可用于有条件地添加新的 CSS 类。对于此指令,我希望以两种不同的背景颜色显示消息,成功消息将具有浅绿色。这需要 Bootstrap 库的 alert-success
CSS 类(与 alert
类结合使用)。我还希望警告或错误消息显示为红色背景。这需要使用 alert-danger
类。使用 ngClass
的优点是可以在运行时将其中一个类添加到 CSS 类定义中
...
<div class="alert info-display"
ng-class="{ 'alert-success': opSuccess, 'alert-danger': !opSuccess }"
ng-click="info.clickOnStatus()">{{msgToShow}}</div>
...
总的来说,此指令的标记代码与 AngularJS 控制器的任何 UI 视图元素没有任何不同。区别在于指令的 JavaScript 代码,将在下一节中展示。
指令的定义
在本节中,我将展示指令的定义方式。让我给您看完整的源代码。它定义在名为 infoDisplay.js 的文件中
(function () {
"use strict";
var mod = angular.module("infoDisplayModule", [ ]);
mod.directive("infoDisplay", [ function () {
var infoDisplayController = ["$scope", function ($scope) {
var info = this;
info.clickOnStatus = function () {
$scope.msgToShow = "";
$scope.opSuccess = false;
};
}];
return {
restrict: "EA",
templateUrl: "/assets/app/pages/infoDisplay.html",
scope: {
msgToShow: "=",
opSuccess: "="
},
controller: infoDisplayController,
controllerAs: "info"
};
}])
...
})();
这只是文件的一部分。该文件的第二部分定义了一个服务工厂,将在下一节中讨论。上面的代码片段定义了指令。指令的定义方式可以分几步解释。第一步是指令的声明,如下所示
var mod = angular.module("infoDisplayModule", [ ]);
mod.directive("infoDisplay", [ function () {
...
...
}])
这定义了一个名为“infoDisplayModule
”的 AngularJS 模块。然后,声明了一个名为“infoDisplay
”的指令,作为该新模块的一部分。指令声明接受指令名称和一个依赖组件数组。在此类中,没有其他依赖组件。依赖注入将用于指令的控制器。
下一步是让指令返回一个对象,其中包含 AngularJS 渲染所需的所有信息。这是指令声明末尾的 return 语句
var mod = angular.module("infoDisplayModule", [ ]);
mod.directive("infoDisplay", [ function () {
...
...
return {
restrict: "EA",
templateUrl: "/assets/app/pages/infoDisplay.html",
scope: {
msgToShow: "=",
opSuccess: "="
},
controller: infoDisplayController,
controllerAs: "info"
};
}])
看起来很简单,但有几点您必须了解。第一行是 restrict: "EA";
。这告诉 AngularJS 该指令只能作为元素(值“E
”)或元素的属性(值“A
”)出现在 HTML 标签中。第二行告诉 AngularJS HTML 模板(在前一节中显示)的位置。
第三部分是 scope
属性。这是用于定义隔离作用域。它并非绝对必要。但是,隔离作用域允许在同一区域放置相同指令的多个副本,每个副本都可以拥有一个隔离作用域来包含其自身的显示模型数据。缺点是它们会占用更多浏览器内存并运行较慢。当您只需要特定区域的一个副本,并且希望它运行快速时,可以忽略添加隔离作用域。在这种情况下,我们不能忽略它。我们需要隔离作用域,以便将数据模型从宿主 UI 注入到此指令中
var mod = angular.module("infoDisplayModule", [ ]);
mod.directive("infoDisplay", [ function () {
...
...
return {
...
scope: {
msgToShow: "=",
opSuccess: "="
},
...
};
}])
隔离作用域有两个注入的数据属性。一个称为 msgToShow
。另一个称为 opSuccess
。分配给这两个属性的值是等号。这意味着绑定到这些作用域数据模型的 数据模型是双向通信的。主机中的更改几乎会立即影响绑定的 数据模型。指令中 数据模型的变化也几乎会立即影响主机的数据模型。这对于我的指令非常有用,因为主机区域应该设置操作是否成功以及消息应该是什么。如果主机重置这两个值,警告条就会消失。从指令的角度来看,用户可以单击警告条,警告条就会消失。此单击会重置这两个值,这些值也会影响主机区域。双向数据值交换确保了清除和值设置能够无缝地来回进行。还有单向绑定和将主机控制器方法引用传递给指令,以便指令可以直接调用方法。我在这里不涵盖它们。
最后一部分是定义一个控制器,并定义控制器如何访问视图模型属性进行显示。请记住隔离作用域。它将作为 $scope
对象注入到此控制器中。这里也是为用户可以单击警告条以关闭警告条的事件添加事件处理程序的地方。这是
var mod = angular.module("infoDisplayModule", [ ]);
mod.directive("infoDisplay", [ function () {
var infoDisplayController = ["$scope", function ($scope) {
var info = this;
info.clickOnStatus = function () {
$scope.msgToShow = "";
$scope.opSuccess = false;
};
}];
...
}])
此控制器的定义有点不同。它是一个数组。第一个是控制器所需的依赖项,即 $scope
。下一个是在初始化期间运行的方法。在此初始化函数中,我获取 this
引用的引用,并将其分配给一个名为“info
”的作用域引用变量。此“info
”引用将用作主视图模型。然后,我定义了名为 info.clickOnStatus()
的单击事件处理程序,它将绑定到警告条 div
的 ngClick
。
事件处理程序会重置双向绑定的属性 $scope.msgToShow
和 $scope.opSuccess
。还记得隔离作用域吗?这就是 $scope
引用的内容。当这两个属性被重置时,更改也会反映在主机控制器中。然后,警告条就会消失,因为 ngIf
指令会条件性地将其添加到 HTML DOM 树并条件性地移除它。
附加奖励:将工厂作为服务
在同一个 js 文件 infoDisplay.js 中,还有一个 AngularJS 工厂的声明。工厂用于创建对象。通常使用工厂创建服务对象,然后服务对象可用于提供基本操作。我使用工厂来创建可以在多个地方使用的服务,并消除重复代码。
在我继续之前,我想展示工厂的完整源代码。这是
...
.factory("infoDisplayService", [ function () {
var svc = {
initStatusDisplay: initStatusDisplay,
setStatusDisplay: setStatusDisplay
};
function initStatusDisplay(vm) {
if (vm != null) {
vm.opSuccess = false;
vm.statusMessage = null;
}
}
function setStatusDisplay(vm, opSuccess, statusMsg) {
if (vm != null) {
vm.opSuccess = opSuccess;
vm.statusMessage = statusMsg;
}
}
return svc;
}]);
...
此工厂创建一个具有两个方法的对象。第一个称为 initStatusDisplay()
。它可用于向任何视图模型添加两个属性(vm.opSuccess
和 vm.statusMessage
)。我将在下一节中解释什么是视图模型。一旦这两个属性被添加到控制器视图模型中,它们就可以双向绑定到指令。同样,将在下一节中解释。
第二个方法称为 setStatusDisplay()
。这是可以在任何地方或任何时候更改状态消息的方法。该方法首先检查以确保目标控制器的视图模型可用,然后它将更改视图模型属性 vm.opSuccess
和 vm.statusMessage
的值。现在,指令设计的所有内容都已就位,是时候将其放入示例应用程序并进行测试了。
示例应用程序——如何使用此指令
就是这样。最后一部分。在本节中,我想讨论指令如何集成到示例应用程序中。HTML 标记定义在名为 index.html 的文件中。我们已经在本教程开头看到过该指令的标记
<div info-display op-success="vm.opSuccess" msg-to-show="vm.statusMessage" ></div>
指令作为属性添加到此 div
中。还记得指令名称按约定称为“infoDisplay
”吗?当它在 HTML 文件中使用时,指令名称会转换为“info-display
”。该指令对两个属性 opSuccess
和 msgToShow
使用双向绑定。它们通过此方式完成
<div ... op-success="vm.opSuccess" msg-to-show="vm.statusMessage" ></div>
按照约定,属性 opSuccess
被称为 op-success
,属性 msgToShow
被称为 msg-to-show
。它们绑定到视图模型的 vm.opSuccess
和 vm.statusMessage
。这些不会在应用程序控制器中找到。它们由上一节中讨论的服务添加到视图模型中。
现在,让我们看看 AngularJS 应用程序。这是应用程序的完整代码
(function () {
"use strict";
var mod = angular.module("testSampleApp", [ "infoDisplayModule" ]);
mod.controller("testSampleController", [ "$scope", "infoDisplayService",
function ($scope, infoDisplayService) {
var vm = this;
infoDisplayService.initStatusDisplay(vm);
vm.userEmail = null;
vm.userPassword = null;
vm.userPassword2 = null;
vm.userFullName = null;
vm.userAddress = null;
vm.userCity = null;
vm.userState = null;
vm.userZipCode = null;
vm.doSubmit = function () {
if (validateInput()) {
infoDisplayService.setStatusDisplay(vm, true,
"User registration request has been processed successfully.");
}
};
vm.doClear = function () {
vm.userEmail = "";
vm.userPassword = "";
vm.userPassword2 = "";
vm.userFullName = "";
vm.userAddress = "";
vm.userCity = "";
vm.userState = "";
vm.userZipCode = "";
infoDisplayService.setStatusDisplay(vm, false, "");
};
function validateInput() {
if (vm.userEmail == null || vm.userEmail.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User email cannot be null or empty.");
return false;
}
if (vm.userPassword == null || vm.userPassword.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User password cannot be null or empty.");
return false;
}
if (vm.userPassword2 == null || vm.userPassword2.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"Re-typed user password cannot be null or empty.");
return false;
}
if (vm.userPassword2 !== vm.userPassword) {
infoDisplayService.setStatusDisplay(vm, false, "Passwords mismatch.");
return false;
}
if (vm.userFullName == null || vm.userFullName.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User full name cannot be null or empty.");
return false;
}
if (vm.userAddress == null || vm.userAddress.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address line cannot be null or empty.");
return false;
}
if (vm.userCity == null || vm.userCity.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address city cannot be null or empty.");
return false;
}
if (vm.userState == null || vm.userState.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address state cannot be null or empty.");
return false;
}
if (vm.userZipCode == null || vm.userZipCode.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address zip code cannot be null or empty.");
return false;
}
return true;
}
}
]);
})();
让我们从 AngularJS 模块定义和应用程序控制器的声明开始
var mod = angular.module("testSampleApp", [ "infoDisplayModule" ]);
mod.controller("testSampleController", [ "$scope", "infoDisplayService",
function ($scope, infoDisplayService) {
...
}
]);
在这里,我必须注入定义指令的模块。注入的模块称为“infoDisplayModule
”。对于控制器,我将注入服务(infoDisplayService
)。
接下来是视图模型属性声明
...
var vm = this;
infoDisplayService.initStatusDisplay(vm);
vm.userEmail = null;
vm.userPassword = null;
vm.userPassword2 = null;
vm.userFullName = null;
vm.userAddress = null;
vm.userCity = null;
vm.userState = null;
vm.userZipCode = null;
...
我使用“vm
”作为视图模型引用,它指向对象的 this
引用。服务 infoDisplayService
将为视图模型创建 opSuccess
和 statusMessage
。然后是用户注册表单的若干属性。
当用户点击“提交”按钮时,此示例应用程序将执行视图模型属性的数据验证。如果其中任何一个无效,提交将触发错误消息显示。如果一切正常,则会显示成功消息。通过警告条进行验证和显示状态消息是在“提交”按钮的事件处理程序方法中完成的。
vm.doSubmit = function () {
if (validateInput()) {
infoDisplayService.setStatusDisplay
(vm, true, "User registration request has been processed successfully.");
}
};
如您所见,该服务将负责设置操作成功和错误/警告状态消息,尽管我必须传入整个视图模型。但该服务只会更改视图模型的两个属性 vm.opSuccess
和 vm.statusMessage
的值。
在此事件处理程序中,有一个 validateInput()
方法执行输入值验证
function validateInput() {
if (vm.userEmail == null || vm.userEmail.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false, "User email cannot be null or empty.");
return false;
}
if (vm.userPassword == null || vm.userPassword.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false, "User password cannot be null or empty.");
return false;
}
if (vm.userPassword2 == null || vm.userPassword2.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"Re-typed user password cannot be null or empty.");
return false;
}
if (vm.userPassword2 !== vm.userPassword) {
infoDisplayService.setStatusDisplay(vm, false, "Passwords mismatch.");
return false;
}
if (vm.userFullName == null || vm.userFullName.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User full name cannot be null or empty.");
return false;
}
if (vm.userAddress == null || vm.userAddress.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address line cannot be null or empty.");
return false;
}
if (vm.userCity == null || vm.userCity.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address city cannot be null or empty.");
return false;
}
if (vm.userState == null || vm.userState.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address state cannot be null or empty.");
return false;
}
if (vm.userZipCode == null || vm.userZipCode.length <= 0) {
infoDisplayService.setStatusDisplay(vm, false,
"User address zip code cannot be null or empty.");
return false;
}
return true;
}
如您所见,这非常直接,对于每个输入属性,值是否符合预期?如果值无效,将显示错误消息。验证方法将立即返回 false
。此方法将返回 false
。当一切检查通过时,该方法返回 true
。
如何测试示例应用程序
下载源代码后,请进入 src/main/resources/static/assets 的文件夹和子文件夹,然后将 *.sj 文件重命名为 *.js。
这是一个基于 Spring Boot 的应用程序,要编译整个项目,请在可以找到 POM.xml 的根目录下运行以下命令
mvn clean install
等待构建完成,它将成功。然后使用以下命令运行应用程序
java -jar target/hanbo-agular-warningbar-1.0.1.jar
等待应用程序启动。然后打开 Chrome 或 Firefox,导航到以下 URL
https://:8080
页面加载完成后,它将以分层顺序显示所有结构化的注释,如本教程中显示的第一个截图
如果您能看到这一点,那么示例应用程序已成功构建。随时输入一些值并单击“提交”按钮,看看错误或成功消息会如何显示。
摘要
在本教程中,我提供了一个非常简单的可重用组件供 AngularJS 应用程序使用。据我观察,在页面上显示警告、错误和成功消息的简单 div
非常常见。为了避免在应用程序的不同部分复制相同的 HTML 或 JavaScript 代码,我创建了这个可重用组件,可以在这些重复代码的位置使用。
本教程详细解释了如何定义组件(一个指令)。如何将控制器的视图模型属性传递到指令中,什么是双向绑定,该指令如何利用它。以及如何定义隔离作用域。如果您对 AngularJS 不够了解,这些是陌生的概念。随着您在 AngularJS 方面的经验增加,这些概念将不再那么陌生,并且会变得清晰。
最后,示例应用程序作为参考程序,向您展示了如何将此可重用组件集成到您的应用程序中。它也是一个测试程序,以防您修改了指令并需要测试更改。如果您足够大胆,可以修改指令以满足您的开发需求。对我来说,这足以应对接下来的几个项目。希望您也能从中找到一些用途。感谢阅读本教程。
历史
- 2020 年 7 月 30 日 - 初稿