如何使用 ControlValueAccessor 在 Angular 中创建自定义表单控件

简介: 如何使用 ControlValueAccessor 在 Angular 中创建自定义表单控件

简介

在 Angular 中创建表单时,有时您希望拥有一个不是标准文本输入、选择或复选框的输入。通过实现 ControlValueAccessor 接口并将组件注册为 NG_VALUE_ACCESSOR,您可以将自定义表单控件无缝地集成到模板驱动或响应式表单中,就像它是一个原生输入一样!

!Rating Input 组件示例的动画 GIF,选择不同数量的星星。

在本文中,您将把一个基本的星级评分输入组件转换为 ControlValueAccessor

先决条件

要完成本教程,您需要:

  • 本地安装 Node.js,您可以按照《如何安装 Node.js 并创建本地开发环境》进行操作。
  • 一些设置 Angular 项目和使用 Angular 组件的基础知识可能会有所帮助。

本教程已在 Node v16.4.2、npm v7.18.1、angular v12.1.1 上进行验证。

步骤 1 — 设置项目

首先,创建一个新的 RatingInputComponent

可以使用 @angular/cli 完成此操作:

ng generate component rating-input --inline-template --inline-style --skip-tests --flat --prefix

这将向应用程序的 declarations 中添加新组件,并生成一个 rating-input.component.ts 文件:

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'rating-input',
  template: `
    <p>
      rating-input works!
    </p>
  `,
  styles: [
  ]
})
export class RatingInputComponent implements OnInit {
  constructor() { }
  ngOnInit(): void {
  }
}

添加模板、样式和逻辑:

import { Component } from '@angular/core';
@Component({
  selector: 'rating-input',
  template: `
    <span
      *ngFor="let starred of stars; let i = index"
      (click)="rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"
    >
      <ng-container *ngIf="starred; else noStar">⭐</ng-container>
      <ng-template #noStar>·</ng-template>
    </span>
  `,
  styles: [`
    span {
      display: inline-block;
      width: 25px;
      line-height: 25px;
      text-align: center;
      cursor: pointer;
    }
  `]
})
export class RatingInputComponent {
  stars: boolean[] = Array(5).fill(false);
  get value(): number {
    return this.stars.reduce((total, starred) => {
      return total + (starred ? 1 : 0);
    }, 0);
  }
  rate(rating: number) {
    this.stars = this.stars.map((_, i) => rating > i);
  }
}

我们可以获取组件的 value(从 05),并通过调用 rate 函数或点击所需的星级来设置组件的值。

您可以将组件添加到应用程序中:

<rating-input></rating-input>

然后运行应用程序:

ng serve

并在 Web 浏览器中进行交互。

这很棒,但我们不能只是将此输入添加到表单中并期望一切立即正常工作。我们需要将其设置为 ControlValueAccessor

步骤 2 — 创建自定义表单控件

为了使 RatingInputComponent 表现得就像是一个原生输入(因此是一个真正的自定义表单控件),我们需要告诉 Angular 如何做一些事情:

  • 写入输入的值 - writeValue
  • 注册一个函数,告诉 Angular 输入的值何时发生变化 - registerOnChange
  • 注册一个函数,告诉 Angular 输入已被触摸 - registerOnTouched
  • 禁用输入 - setDisabledState

这四个内容构成了 ControlValueAccessor 接口,它是表单控件和原生元素或自定义输入组件之间的桥梁。一旦我们的组件实现了该接口,我们需要告诉 Angular 通过提供它作为 NG_VALUE_ACCESSOR 来使用它。

在代码编辑器中重新查看 rating-input.component.ts,并进行以下更改:

import { Component, forwardRef, HostBinding, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
  selector: 'rating-input',
  template: `
    <span
      *ngFor="let starred of stars; let i = index"
      (click)="onTouched(); rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"
    >
      <ng-container *ngIf="starred; else noStar">⭐</ng-container>
      <ng-template #noStar>·</ng-template>
    </span>
  `,
  styles: [`
    span {
      display: inline-block;
      width: 25px;
      line-height: 25px;
      text-align: center;
      cursor: pointer;
    }
  `],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => RatingInputComponent),
      multi: true
    }
  ]
})
export class RatingInputComponent implements ControlValueAccessor {
  stars: boolean[] = Array(5).fill(false);
  // 允许输入被禁用,并在禁用时使其略微透明。
  @Input() disabled = false;
  @HostBinding('style.opacity')
  get opacity() {
    return this.disabled ? 0.25 : 1;
  }
  // 当评分发生变化时调用的函数。
  onChange = (rating: number) => {};
  // 当输入被触摸时(点击星星时)调用的函数。
  onTouched = () => {};
  get value(): number {
    return this.stars.reduce((total, starred) => {
      return total + (starred ? 1 : 0);
    }, 0);
  }
  rate(rating: number) {
    if (!this.disabled) {
      this.writeValue(rating);
    }
  }
  // 允许 Angular 更新模型(评分)。
  // 在这里更新模型和视图所需的更改。
  writeValue(rating: number): void {
    this.stars = this.stars.map((_, i) => rating > i);
    this.onChange(this.value);
  }
  // 允许 Angular 注册一个在模型(评分)更改时调用的函数。
  // 将函数保存为以后调用的属性。
  registerOnChange(fn: (rating: number) => void): void {
    this.onChange = fn;
  }
  // 允许 Angular 注册一个在输入被触摸时调用的函数。
  // 将函数保存为以后调用的属性。
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
  // 允许 Angular 禁用输入。
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }
}

此代码将允许输入被禁用,并在禁用时使其略微透明。

运行应用程序:

ng serve

并在 Web 浏览器中进行交互。

您还可以禁用输入控件:

<rating-input [disabled]="true"></rating-input>

现在我们可以说我们的 RatingInputComponent 是一个自定义表单组件!它将在模板驱动或响应式表单中像任何其他原生输入一样工作(Angular 为这些提供了 ControlValueAccessors!)。

结论

在本文中,您将一个基本的星级评分输入组件转换为了 ControlValueAccessor

现在您会注意到:

  • ngModel 现在可以正常工作。
  • 我们可以添加自定义验证。
  • 控件状态和有效性可以通过 ngModel 获得,比如 ng-dirtyng-touched 类。

如果您想了解更多关于 Angular 的知识,请查看我们的 Angular 专题页面,了解练习和编程项目。


目录
相关文章
|
3月前
|
前端开发 开发者 C#
深度解析 Uno Platform 中的 MVVM 模式:从理论到实践的全方位指南,助你轻松掌握通过 C# 与 XAML 构建高效可维护的跨平台应用秘籍
【8月更文挑战第31天】本文详细介绍如何在优秀的跨平台 UI 框架 Uno Platform 中实施 MVVM(Model-View-ViewModel)模式,通过一个简单的待办事项列表应用演示其实现过程。MVVM 模式有助于分离视图层与业务逻辑层,提升代码组织性、易测性和可维护性。Uno Platform 的数据绑定机制使视图与模型间的同步变得高效简便。文章通过构造 `TodoListViewModel` 类及其相关视图,展示了如何解耦视图与模型,实现动态数据绑定及命令处理,从而提高代码质量和开发效率。通过这一模式,开发者能更轻松地构建复杂的跨平台应用。
48 0
|
3月前
|
开发者 Java Spring
JSF 牵手社交登录,如魔法风暴席卷 Web 世界,开启震撼便捷登录之旅!
【8月更文挑战第31天】在互联网时代,便捷登录成为用户的核心需求。社交登录凭借其便捷性、安全性和社交化的特点,在各类Web应用中广泛应用。JavaServer Faces(JSF),作为一款流行的Java Web框架,能够轻松集成社交登录功能,显著提升用户体验。本文详细介绍社交登录的优势,并提供两种JSF集成社交登录的常见方法:一是利用Spring Social等第三方库简化开发;二是自行实现社交登录流程。开发者可根据项目需求选择适合的方案。
39 0
|
3月前
|
开发者 容器 Docker
JSF与Docker,引领容器化浪潮!让你的Web应用如虎添翼,轻松应对高并发!
【8月更文挑战第31天】在现代Web应用开发中,JSF框架因其实用性和灵活性被广泛应用。随着云计算及微服务架构的兴起,容器化技术变得日益重要,Docker作为该领域的佼佼者,为JSF应用提供了便捷的部署和管理方案。本文通过基础概念讲解及示例代码展示了如何利用Docker容器化JSF应用,帮助开发者实现高效、便携的应用部署。同时也提醒开发者注意JSF与Docker结合使用时可能遇到的限制,并根据实际情况做出合理选择。
40 0
|
3月前
Angular 中的响应式表单:监听变化
Angular 中的响应式表单:监听变化
34 0
|
3月前
|
JavaScript 前端开发
如何在 Angular 中使用响应式表单
如何在 Angular 中使用响应式表单
18 0
|
3月前
|
JavaScript 前端开发
如何在 Angular 中为响应式表单创建自定义验证器
如何在 Angular 中为响应式表单创建自定义验证器
23 0
|
3月前
|
JavaScript
如何在自定义 Angular 指令中使用 @HostBinding 和 @HostListener
如何在自定义 Angular 指令中使用 @HostBinding 和 @HostListener
32 0
|
3月前
在Angular中创建自定义管道
在Angular中创建自定义管道
16 0
|
3月前
|
JavaScript 安全
如何在 Angular Material 中使用自定义 SVG 图标
如何在 Angular Material 中使用自定义 SVG 图标
63 0
|
2月前
|
缓存 JavaScript 前端开发
Angular 应用打包和部署
Angular 应用打包和部署
167 58