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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (2投票s)

2016 年 12 月 15 日

CPOL

5分钟阅读

viewsIcon

13363

downloadIcon

175

解释使用 Angular 2 构建基本自定义用户界面控件的过程

Sample Image - maximum width is 600 pixels

引言

本文旨在介绍如何构建一个非常基础的自定义用户界面控件组件,该组件可以在其他组件中重用。我假设您已经具备设置 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 项目使用以下结构

Sample Image - maximum width is 600 pixels

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(用于指定占位符)

接下来,我们有一个 onTouchedCallbackonChangeCallback,它们被设置为在导入语句之后不久创建为 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 模板

Sample Image - maximum width is 600 pixels

这是一个非常简单的文本输入控件,我们根据配置的输入参数显示验证规则,我不会深入讨论细节,但有一个非常重要的绑定我想重点介绍

[(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 模板中配置该组件

Sample Image - maximum width is 600 pixels

© . All rights reserved.