如何使用 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(从 0 到 5),并通过调用 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-dirty和ng-touched类。
如果您想了解更多关于 Angular 的知识,请查看我们的 Angular 专题页面,了解练习和编程项目。
相关文章:
如何使用 ControlValueAccessor 在 Angular 中创建自定义表单控件
简介 在 Angular 中创建表单时,有时您希望拥有一个不是标准文本输入、选择或复选框的输入。通过实现 ControlValueAccessor 接口并将组件注册为 NG_VALUE_ACCESSOR,您可以将自定义表单控件无缝地集成到模板驱动或响应式表单中,就像它是一个原…...
视频讲解:优化柱状图
你好,我是郭震 AI数据可视化 第三集:美化柱状图,完整视频如下所示: 美化后效果前后对比,前: 后: 附完整案例源码: util.py文件 import platformdef get_os():os_name platform.syst…...
OpenAI宣布ChatGPT新增记忆功能;谷歌AI助理Gemini应用登陆多地区
🦉 AI新闻 🚀 OpenAI宣布ChatGPT新增记忆功能,可以自由控制内存,提供个性化聊天和长期追踪服务 摘要:ChatGPT新增的记忆功能可以帮助AI模型记住用户的提问内容,并且可以自由控制其内存。这意味着用户不必…...
Solidworks:平面草图练习
继续练习平面草图,感觉基本入门了。...
React18原理: 渲染与更新时的重点关注事项
概述 react 在渲染过程中要做很多事情,所以不可能直接通过初始元素直接渲染还需要一个东西,就是虚拟节点,暂不涉及React Fiber的概念,将vDom树和Fiber 树统称为虚拟节点有了初始元素后,React 就会根据初始元素和其他可…...
嵌入式I2C 信号线为何加上拉电阻(图文并茂)
IIC 是一个两线串行通信总线,包含一个 SCL 信号和 SDA 信号,SCL 是时钟信号,从主设备发出,SDA 是数据信号,是一个双向的,设备发送数据和接收数据都是通过 SDA 信号。 在设计 IIC 信号电路的时候我们会在 SC…...
Vite 5.0 正式发布
11 月 16 日,Vite 5.0 正式发布,这是 Vite 道路上的又一个重要里程碑!Vite 现在使用 Rollup 4,这已经代表了构建性能的大幅提升。此外,还有一些新的选项可以改善开发服务器性能。 Vite 4 发布于近一年前,它…...
嵌入式STM32 单片机 GPIO 的工作原理详解
STM32的 GPIO 介绍 GPIO 是通用输入/输出端口的简称,是 STM32 可控制的引脚。GPIO 的引脚与外部硬件设备连接,可实现与外部通讯、控制外部硬件或者采集外部硬件数据的功能。 以 STM32F103ZET6 芯片为例子,该芯片共有 144 脚芯片,…...
系统调用的概念
在嵌入式开发、操作系统开发以及一般的系统编程中,系统调用是一个核心概念。它允许用户空间程序请求内核执行某些操作,如打开文件、读写数据、创建进程等。这些操作通常需要特殊的权限或访问硬件资源,因此不能直接在用户模式下执行。 系统调…...
【无标题】Matlab 之axes函数——创建笛卡尔坐标区
**基本用法:**axes 在当前图窗中创建默认的笛卡尔坐标区,并将其设置为当前坐标区。 应用场景1:在图窗中放置两个 Axes 对象,并为每个对象添加一个绘图。 要求1:指定第一个 Axes 对象的位置,使其左下角位于…...
2.12:C语言测试题
1.段错误:申请堆区内存未返回,str指向NULL 2.段错误:局部变量,本函数结束,p也释放 3.越界访问,可能正常输出hello,可能报错 4.可能段错误,释放后,str未指向NULL&#x…...
【Linux】yum软件包管理器
目录 Linux 软件包管理器 yum 什么是软件包 Linux安装软件 查看软件包 关于rzsz Linux卸载软件 查看yum源 扩展yum源下载 Linux开发工具 vim编辑器 上述vim三种模式之间的切换总结: 命令模式下,一些命令: vim配置 Linux 软件包管理…...
「优选算法刷题」:寻找旋转排序数组中的最小值
一、题目 已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到: 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]若旋转 7 次…...
MySQL 基础入门指南:从安装到基本操作
一、简介 MySQL 是一种流行的开源关系型数据库管理系统,被广泛用于各种规模和类型的应用程序中。如果您对 MySQL 还不熟悉,本文将为您提供一个基础的入门指南,从安装到基本操作。 1.1 安装 MySQL 首先,您需要下载并安装 MySQL。…...
嵌入式Qt Qt Creator安装与工程介绍
一.Qt概述 什么是Qt:Qt是一个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供建立图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。 二.Qt Creator下载安装 下载地址:Index of /a…...
Windows 系统盘(C盘)爆红如何清理、如何增加C盘空间
1、简介 Windows系统中,系统和保留占用太多的空间,一旦系统盘分配空间较少,使用一段时间后,备份文件、临时文件、系统更新记录等都会在占用系统盘较大空间,导致系统盘空间不够使用,会造成应用运行卡顿。如何…...
【JavaEE Spring】Spring 原理
Spring 原理 1. Bean的作⽤域1.1 概念1.2 Bean的作⽤域 2. Bean的⽣命周期 1. Bean的作⽤域 1.1 概念 在Spring IoC&DI阶段, 我们学习了Spring是如何帮助我们管理对象的. 通过 Controller , Service , Repository , Component , Configuration ,Bean 来声明Bean对象。通…...
【Crypto | CTF】RSA打法
天命:我发现题题不一样,已知跟求知的需求都不一样 题目一:已知 p q E ,计算T,最后求D 已知两个质数p q 和 公钥E ,通过p和q计算出欧拉函数T,最后求私钥D 【密码学 | CTF】BUUCTF RSA-CSDN…...
红衣大叔讲AI:从OpenAI发布首个视频大模型Sora,谈2024年视觉大模型的十大趋势
OpenAI宣布推出全新的生成式人工智能模型“Sora”。据了解,通过文本指令,Sora可以直接输出长达60秒的视频,并且包含高度细致的背景、复杂的多角度镜头,以及富有情感的多个角色。 OpenAI发布首个视频大模型Sora,一句话生…...
java远程连接Linux执行命令的三种方式
java远程连接Linux执行命令的三种方式 1. 使用JDK自带的RunTime类和Process类实现2. ganymed-ssh2 实现3. jsch实现4. 完整代码:执行shell命令下载和上传文件 1. 使用JDK自带的RunTime类和Process类实现 public static void main(String[] args){Process proc Run…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
