如何使用ControlValueAccessor在Angular中创建自定义表单控件
介绍
在 Angular 中创建表单时,有时您希望输入不是标准文本输入、选择或复选框。 通过实现 ControlValueAccessor
接口并将组件注册为 NG_VALUE_ACCESSOR
,您可以将自定义表单控件无缝集成到模板驱动或反应式表单中,就像它是本机输入一样!
在本文中,您将一个基本的星级输入组件转换为 ControlValueAccessor
。
先决条件
要完成本教程,您需要:
- Node.js 安装在本地,您可以按照【X57X】如何安装Node.js 并创建本地开发环境【X126X】进行。
- 熟悉 设置 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
文件:
src/app/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 { } }
添加模板、样式和逻辑:
src/app/rating-input.component.ts
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); } }
我们可以通过调用rate
函数或者点击数量明星想要。
您可以将组件添加到应用程序:
src/app/app.component.html
<rating-input></rating-input>
并运行应用程序:
ng serve
并在网络浏览器中与之交互。
这很好,但我们不能只是将此输入添加到表单中并期望一切正常。 我们需要将其设为 ControlValueAccessor
。
第 2 步 — 创建自定义表单控件
为了使 RatingInputComponent
表现得好像它是一个原生输入(因此,一个真正的自定义表单控件),我们需要告诉 Angular 如何做一些事情:
- 将值写入输入 -
writeValue
- 注册一个函数以在输入值更改时告诉 Angular -
registerOnChange
- 注册一个函数以在输入被触摸时告诉 Angular -
registerOnTouched
- 禁用输入 -
setDisabledState
这四样东西组成了 ControlValueAccessor
接口,是表单控件和原生元素或自定义输入组件之间的桥梁。 一旦我们的组件实现了该接口,我们需要通过将其提供为 NG_VALUE_ACCESSOR
来告知 Angular,以便可以使用它。
在代码编辑器中重新访问 rating-input.component.ts
并进行以下更改:
src/app/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); // Allow the input to be disabled, and when it is make it somewhat transparent. @Input() disabled = false; @HostBinding('style.opacity') get opacity() { return this.disabled ? 0.25 : 1; } // Function to call when the rating changes. onChange = (rating: number) => {}; // Function to call when the input is touched (when a star is clicked). 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); } } // Allows Angular to update the model (rating). // Update the model and changes needed for the view here. writeValue(rating: number): void { this.stars = this.stars.map((_, i) => rating > i); this.onChange(this.value); } // Allows Angular to register a function to call when the model (rating) changes. // Save the function as a property to call later here. registerOnChange(fn: (rating: number) => void): void { this.onChange = fn; } // Allows Angular to register a function to call when the input has been touched. // Save the function as a property to call later here. registerOnTouched(fn: () => void): void { this.onTouched = fn; } // Allows Angular to disable the input. setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } }
此代码将允许禁用输入,并在启用时使其有点透明。
运行应用程序:
ng serve
并在网络浏览器中与之交互。
您还可以禁用输入控件:
src/app/app.component.html
<rating-input [disabled]="true"></rating-input>
我们现在可以说我们的 RatingInputComponent
是一个自定义表单组件! 它将像任何其他本机输入一样工作(Angular 为这些输入提供了 ControlValueAccessors
!),以模板驱动或反应形式。
结论
在本文中,您将一个基本的星级输入组件转换为 ControlValueAccessor
。
你现在会注意到:
如果您想了解有关 Angular 的更多信息,请查看 我们的 Angular 主题页面 以获取练习和编程项目。