使用 Angular 2 构建自定义用户界面控件






4.33/5 (2投票s)
解释使用 Angular 2 构建基本自定义用户界面控件的过程
引言
本文旨在介绍如何构建一个非常基础的自定义用户界面控件组件,该组件可以在其他组件中重用。我假设您已经具备设置 Angular 2 环境的知识,因此本文将不再赘述。我们将创建一个非常简单的文本输入组件,该组件可以配置为:
- 验证是否输入了值(我们可以启用或禁用此验证)
- 验证输入的值长度是否大于等于 2 个字符(我们可以启用或禁用此验证,并使最小长度可配置)
- 提供验证消息
- 提供最大长度
- 提供错误和正确图标类
- 提供占位符
背景
在我最近的几个项目中,我意识到我一遍又一遍地重写相同的用户界面组件。我大部分时间都花在编写带有相关验证规则的输入控件上,而不是专注于像应用程序业务规则这样重要的开发。请不要误会,输入控件和客户端验证也至关重要,并且是许多开发人员忽略的地方,但我坚信这是可以应用 80/20 法则的领域之一。付出 20% 的工作量可以为我们节省 80% 的应用程序开发总时间。而且由于这些组件是可重用的,它将对未来的项目时间表产生巨大的时间影响。
在我们开始之前,我必须提到一篇我在编写第一个自定义组件时广泛使用的文章:Connect your custom control to ngModel with Control Value Accessor。我从这篇文章中学到了大多数重要概念,并将其重构为适用于我自己的独特项目的示例。
使用的软件版本
- Angular 2.1.1
- Bootstrap 3.3.7(请注意,Bootstrap 不是必需的)
- Font-Awesome 4.7.0(请注意,Font-Awesome 不是必需的)
Angular 2 项目结构
我为我的 Angular 2 项目使用以下结构
TextInputComponent.ts
让我们深入研究并开始编写可重用的文本输入组件代码。下面是完整的 TypeScript 文件,我将逐段解释
import { Component, forwardRef, Input } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; const noop = () => { }; export const textInputValueAccessor: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TextInputComponent), multi: true }; @Component({ selector: 'TextInput', moduleId: module.id, templateUrl: './Templates/TextInput.html', providers: [textInputValueAccessor] }) export class TextInputComponent implements ControlValueAccessor { @Input() ValidationOkFontClass: string; @Input() ValidationErrorFontClass: string; @Input() ValidationRuleRequired: boolean; @Input() ValidationRuleRequiredDescription: string; @Input() ValidationRuleMinimumLength: boolean; @Input() MinimumLength: number; @Input() ValidationRuleMinimumLengthDescription: string; @Input() MaximumLength: number; @Input() PlaceHolder: string; InnerValue: any = ''; private onTouchedCallback: () => void = noop; private onChangeCallback: (_: any) => void = noop; get value(): any { return this.InnerValue; }; set value(v: any) { if (v !== this.InnerValue) { this.InnerValue = v; this.onChangeCallback(v); } } writeValue(value: any) { if (value !== this.InnerValue) { this.InnerValue = value; } } registerOnChange(fn: any) { this.onChangeCallback = fn; } registerOnTouched(fn: any) { this.onTouchedCallback = fn; } }
第一步是告知 NG_VALUE_ACCESSOR
使用哪个组件进行数据绑定。我们通过导入 Angular forms 包中的 NG_VALUE_ACCESSOR
来实现这一点
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
并创建一个可以由我们的组件使用的自定义值访问器
export const textInputValueAccessor: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TextInputComponent), multi: true };
接下来,我们必须设置我们的组件装饰器,我们在其中将 textInputValueAccessor
指定为提供者
@Component({ selector: 'TextInput', moduleId: module.id, templateUrl: './Templates/TextInput.html', providers: [textInputValueAccessor] })
现在我们创建实现 ControlValueAccessor
接口的类,该接口公开了我们将需要连接 ngModel
到我们的组件的函数。
export class TextInputComponent implements ControlValueAccessor
ControlValueAccessor
接口要求实现三个函数
writeValue(obj: any)
: void (用于向元素写入新值)registerOnChange(fn: any)
: void (注册在更改事件发生时要调用的函数)registerOnTouched(fn: any)
: void (注册在触摸事件发生时要调用的函数)
这些函数很简单,本文将不再详细讨论。有关更多信息,您可以阅读ControlValueAccessor Angular 文档。
我们希望使此组件尽可能可配置,以便在其他组件或项目中轻松重用,因此让我们创建可以在设置组件时配置的输入参数
@Input() ValidationOkFontClass: string; @Input() ValidationErrorFontClass: string; @Input() ValidationRuleRequired: boolean; @Input() ValidationRuleRequiredDescription: string; @Input() ValidationRuleMinimumLength: boolean; @Input() MinimumLength: number; @Input() ValidationRuleMinimumLengthDescription: string; @Input() MaximumLength: number; @Input() PlaceHolder: string;
输入参数将用于
ValidationOkFontClass
(用于指定验证成功时图标使用的类)ValidationErrorFontClass
(用于指定验证失败时图标使用的类)ValidationRuleRequired
(用于指定是否需要值)ValidationRuleRequiredDescription
(用于指定当需要值时显示的错误消息)ValidationRuleMinimumLength
(用于指定是否应对提供的值应用最小长度验证)ValidationRuleMinimumLengthDescription
(用于指定当应用最小长度验证时显示的错误消息)MaximumLength
(用于指定在应用最小长度验证时所需的最小长度)PlaceHolder
(用于指定占位符)
接下来,我们有一个 onTouchedCallback
和 onChangeCallback
,它们被设置为在导入语句之后不久创建为 const 的 noop
函数。
const noop = () => { }; private onTouchedCallback: () => void = noop; private onChangeCallback: (_: any) => void = noop;
我记得第一次看到这个时感到很困惑。为什么我们要将这些回调函数分配给一个什么都不做的虚拟函数?不要太担心,虚拟函数只是回调函数的占位符,直到它们被 Angular 注册。
创建 TextInputComponent
的最后一步是创建 getter 和 setter 函数,它们将使我们能够将组件值绑定到 ngModel。
get value(): any { return this.InnerValue; }; set value(v: any) { if (v !== this.InnerValue) { this.InnerValue = v; this.onChangeCallback(v); } }
TextInput.html
现在我们已经完成了 TextInputComponent
,我们可以创建 html 模板
这是一个非常简单的文本输入控件,我们根据配置的输入参数显示验证规则,我不会深入讨论细节,但有一个非常重要的绑定我想重点介绍
[(ngModel)]="value"
在这里,我们将 ngModel 绑定到 TextInputComponent
的值 getter 和 setter 函数
TextInputModule.ts
现在我们来处理枯燥的工作,创建 TextInputModule
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { TextInputComponent } from '../Components/TextInputComponent'; @NgModule({ imports: [BrowserModule, FormsModule], declarations: [TextInputComponent], exports: [TextInputComponent], bootstrap: [TextInputComponent] }) export class TextInputModule { }
确保组件已导出,这将使组件可供其他组件重用
exports: [TextInputComponent]
如何使用该组件?
首先,在将使用该组件的组件的模块类中导入该组件,我们称之为 SomeComponent
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { SomeComponent } from '../Components/SomeComponent'; import { TextInputModule } from '../Modules/TextInputModule'; @NgModule({ imports: [BrowserModule, FormsModule, HttpModule, TextInputModule], declarations: [SomeComponent], bootstrap: [SomeComponent] }) export class SomeModule { }
然后,在 SomeComponent
的 HTML 模板中配置该组件