简介
当在Angular中创建表单时,有时你希望有一个不是标准文本输入、选择或复选框的输入。通过实现ControlValueAccessor
接口并将组件注册为NG_VALUE_ACCESSOR
,您可以将自定义表单控件无缝集成到模板驱动或响应式表单中,就像它是本机输入一样!
的评级输入组件示例的动画gif
在本文中,您将把一个基本的星级输入组件转换成一个ControlValueAccessor
。
前提条件
要完成本教程,您需要:
- 本地安装node.js,可按照如何安装node.js并创建本地开发Environment.
- 熟悉设置角度project和使用角度components可能会有所帮助。
本教程使用Node v16.4.2、npm
v7.18.1、angular
v12.1.1进行了验证。
第一步-设置项目
首先创建一个新的RatingInputComponent
。
这可以通过@angular/cli
来实现:
1ng generate component rating-input --inline-template --inline-style --skip-tests --flat --prefix
这会将新组件添加到APP声明
中,并生成一个Rating-input.Component.ts
文件:
1[label src/app/rating-input.component.ts]
2import { Component, OnInit } from '@angular/core';
3
4@Component({
5 selector: 'rating-input',
6 template: `
7 <p>
8 rating-input works!
9 </p>
10 `,
11 styles: [
12 ]
13})
14export class RatingInputComponent implements OnInit {
15
16 constructor() { }
17
18 ngOnInit(): void {
19 }
20
21}
添加模板、样式和逻辑:
1[label src/app/rating-input.component.ts]
2import { Component } from '@angular/core';
3
4@Component({
5 selector: 'rating-input',
6 template: `
7 <span
8 *ngFor="let starred of stars; let i = index"
9 (click)="rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"
10 >
11 <ng-container *ngIf="starred; else noStar">⭐</ng-container>
12 <ng-template #noStar>·</ng-template>
13 </span>
14 `,
15 styles: [`
16 span {
17 display: inline-block;
18 width: 25px;
19 line-height: 25px;
20 text-align: center;
21 cursor: pointer;
22 }
23 `]
24})
25export class RatingInputComponent {
26 stars: boolean[] = Array(5).fill(false);
27
28 get value(): number {
29 return this.stars.reduce((total, starred) => {
30 return total + (starred ? 1 : 0);
31 }, 0);
32 }
33
34 rate(rating: number) {
35 this.stars = this.stars.map((_, i) => rating > i);
36 }
37}
我们可以通过调用rate
函数或点击想要的星数来获取组件的Value
(0到
5`),并设置组件的值。
您可以将组件添加到应用程序中:
1[label src/app/app.component.html]
2<rating-input></rating-input>
并运行应用程序:
1ng serve
并在网络浏览器中与其交互。
这很好,但我们不能只将此输入添加到表单中,然后期望一切都能正常工作。我们需要将其设置为ControlValueAccessor
。
第二步-创建自定义表单控件
为了使`RatingInputComponent‘的行为像是本机输入(从而成为真正的定制表单控件),我们需要告诉ANGLE如何做一些事情:
- 在输入中写入一个值-
WriteValue
- 注册一个函数,在输入值发生变化时通知ANGLE-
registerOnChange
- 注册一个函数,告知ANGLE何时触摸输入-
registerOnTouched
- 关闭输入-
setDisabledState
这四个组件组成了ControlValueAccessor接口,它是表单控件和本机元素或自定义输入组件之间的桥梁。一旦我们的组件实现了该接口,我们需要通过将其提供为NG_VALUE_ACCESSOR
来告诉Angular它,以便它可以使用。
在代码编辑器中重新访问Rating-input.Component.ts
并进行以下更改:
1[label src/app/rating-input.component.ts]
2import { Component, forwardRef, HostBinding, Input } from '@angular/core';
3import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
4
5@Component({
6 selector: 'rating-input',
7 template: `
8 <span
9 *ngFor="let starred of stars; let i = index"
10 (click)="onTouched(); rate(i + (starred ? (value > i + 1 ? 1 : 0) : 1))"
11 >
12 <ng-container *ngIf="starred; else noStar">⭐</ng-container>
13 <ng-template #noStar>·</ng-template>
14 </span>
15 `,
16 styles: [`
17 span {
18 display: inline-block;
19 width: 25px;
20 line-height: 25px;
21 text-align: center;
22 cursor: pointer;
23 }
24 `],
25 providers: [
26 {
27 provide: NG_VALUE_ACCESSOR,
28 useExisting: forwardRef(() => RatingInputComponent),
29 multi: true
30 }
31 ]
32})
33export class RatingInputComponent implements ControlValueAccessor {
34 stars: boolean[] = Array(5).fill(false);
35
36 // Allow the input to be disabled, and when it is make it somewhat transparent.
37 @Input() disabled = false;
38 @HostBinding('style.opacity')
39 get opacity() {
40 return this.disabled ? 0.25 : 1;
41 }
42
43 // Function to call when the rating changes.
44 onChange = (rating: number) => {};
45
46 // Function to call when the input is touched (when a star is clicked).
47 onTouched = () => {};
48
49 get value(): number {
50 return this.stars.reduce((total, starred) => {
51 return total + (starred ? 1 : 0);
52 }, 0);
53 }
54
55 rate(rating: number) {
56 if (!this.disabled) {
57 this.writeValue(rating);
58 }
59 }
60
61 // Allows Angular to update the model (rating).
62 // Update the model and changes needed for the view here.
63 writeValue(rating: number): void {
64 this.stars = this.stars.map((_, i) => rating > i);
65 this.onChange(this.value);
66 }
67
68 // Allows Angular to register a function to call when the model (rating) changes.
69 // Save the function as a property to call later here.
70 registerOnChange(fn: (rating: number) => void): void {
71 this.onChange = fn;
72 }
73
74 // Allows Angular to register a function to call when the input has been touched.
75 // Save the function as a property to call later here.
76 registerOnTouched(fn: () => void): void {
77 this.onTouched = fn;
78 }
79
80 // Allows Angular to disable the input.
81 setDisabledState(isDisabled: boolean): void {
82 this.disabled = isDisabled;
83 }
84}
这段代码将允许禁用输入,当它被禁用时,它将变得有些透明。
运行应用程序:
1ng serve
并在网络浏览器中与其交互。
您还可以禁用输入控件:
1[label src/app/app.component.html]
2<rating-input [disabled]="true"></rating-input>
我们现在可以说我们的RatingInputComponent
是一个自定义表单组件!它将像任何其他本机输入一样工作(ANGLE为这些输入提供了ControlValueAccessors
!)以模板驱动或反应性形式。
结论
在本文中,您将一个基本的星级输入组件转换为一个ControlValueAccessor
。
现在你会注意到:
ngModel
只是起作用
。- 我们可以添加自定义validation.
- 通过
ngModel
提供控件状态和validity,如ng-dirty
、ng-Touched
类。
如果你想了解更多关于Angular的知识,请查看我们的Angular主题页面获取练习和编程项目。