Angular中的自定义控件

序言


让我们谈谈角度的反应形式,学习自定义控件如何创建,使用和验证它们。 本文假定您已经熟悉了Angular框架,但想深入了解其具体细节。 祝好,让我们开始吧。

反应式和模板驱动形式


请确保我们在同一页面上。 角形具有两种类型的形式: 模板驱动形式反应形式

模板驱动表单是基于双向绑定的表单(hi,angularjs)。 我们在类中指定字段(例如,用户名),在输入标签的html中绑定[[value]] =“ username”,并且当输入值更改时,用户名值也会更改。 在2011年,那真是太神奇了! 好的,但是有细微差别。 这样,构建复杂的形状将非常困难。

反应性表单是用于构建简单和复杂表单的便捷工具。 他们告诉我们,“创建表单类的实例( FormGroup ),将控件的实例( FormControl )传递给它,然后将其交给html,剩下的我会做。” 好吧,让我们尝试:

class UsefullComponent { public control = new FormControl(''); public formG = new FormGroup({username: control}); } 

 <form [formGroup]="formG"> <input type="text" formControlName="username"> </form> 

结果,我们得到了一个带有二十一点的反应式表格,而且……嗯,您知道了所有便利设施。 例如, formG.valueChanges将为我们提供一个可观察的(流)表单更改。 您还可以添加新控件,删除现有控件,更改验证规则,获取表单值( formG.value )等。 所有这些都与一个formG实例一起工作

为了避免每次都手动创建上述类的实例, Angular开发人员为我们提供了一个方便的FormBuilder ,借助该工具,可以将上面的示例重写为

 class UsefullComponent { public formG: FormGroup; constructor(private fb: FormBuilder) { this.formG = fb.group({ name: '' //  new FormControl() !! }) } } 

自定义控件


Angular告诉我们“兄弟,如果您没有足够的本机控件(输入,日期,数字和其他控件),或者如果您根本不喜欢它们的某些功能,那么这是一个简单但功能强大的工具,可用于创建您自己的控件。” 自定义控件甚至可以用于响应式,甚至可以用于模板驱动的表单,并且可以使用指令来实现(令人惊讶!)。 知道组件是带有模板的指令(所需)后,我们编写:

 import { Component, Input } from '@angular/core'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent { @Input() value = 0; up() { this.value++; } down() { this.value - ; } } 

像任何指令一样,它必须在模块中声明(这样,文章就不会增加,我们将忽略这些细微差别)。

现在我们可以使用创建的组件:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control></counter-control> ` }) class ParentComponent {} 

当然,所有方法都可以,但是表格在哪里? 至少是模板驱动的。
不必担心,遇到ControlValueAccessor之后一切都会发生。

控制值访问器


为了使角度机制与您的自定义控件进行交互,您需要此控件来实现特定的接口。 此接口称为ControlValueAccessor 。 当我们的对象(在这种情况下,控件)实现一个接口,而其他对象(角度形式)通过该接口与我们的对象交互时,这是OOP中多态性的一个很好的例子。

多亏了ControlValueAccessor ,我们有了使用控件的单一方法,顺便说一句,它不仅用于创建自定义控件。 引擎盖下的Angular也使用此接口。 为了什么 并且只是为了使本机控件的行为具有统一的外观。 例如,在输入中,值包含在value属性中,在复选框中,该值是通过选中的属性确定的。 因此,每种控件都有其自己的ControlValueAccessor:用于输入和文本区域的DefaultValueAccessor,用于复选框的CheckboxControlValueAccessor,用于单选按钮的RadioControlValueAccessor等。

但是使用ControlValueAccessor角度会做什么? 一切都非常简单,它将来自模型的值写入DOM(视图),还将更改控制事件引发给FormGroup和其他指令。

现在我们已经了解了ControlValueAccessor ,我们可以将其应用于控件。

让我们看一下它的界面:

 interface ControlValueAccessor { writeValue(value: any): void registerOnChange(fn: any): void registerOnTouched(fn: any): void } 

writeValue(值:任意) -设置源( 新的FormControl(“我是默认值”) )或新的值超过control.setValue(“我的设置值”)时调用

registerOnChange(fn:any) -定义值更改时应调用的处理程序的方法(fn是一个回调,它将通知表单该控件中的值已更改)。

registerOnTouched(fn:any) -定义在blur事件上调用的回调(控件带有touched标记)

 import { Component, Input } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent implements ControlValueAccessor { @Input() value = 0; onChange(_: any) {} up() { this.value++; } down() { this.value - ; } writeValue(value: any) { this.value = value; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched() {} } 


为了使父窗体知道控件中的更改,我们需要为value的每个更改调用onChange方法。 为了不在每个方法(向上和向下)中编写onChange调用,我们通过getter和setter来实现value字段:

 // … class CounterControlComponent implements ControlValueAccessor { private _value; get value() { return this._value; } @Input() set value(val) { this._value = val; this.onChange(this._value); } onChange(_: any) {} up() { this.value++; } down() { this.value - ; } // … } 

目前, Angular不知道实现ControlValueAccessor的组件应被视为自定义控件,让我们告诉他:

 import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ … providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterControlComponent), multi: true }] }) class CounterControlComponent implements ControlValueAccessor { … } 

在代码的这一部分中,我们对角度说“我想注册一个新的自定义控件( 提供:NG_VALUE_ACCESSOR ),使用该组件的现有实例( 此时将初始化我们的组件)( useExisting:forwardRef(()=> CounterControlComponent )” 值)

multi:true表示使用此类标记( NG_VALUE_ACCESSOR )可能存在多个依赖项。

不只是创建


现在该zayuzat我们的自定义控件了。 不要忘记将FormsModule / ReactiveFormsModule添加到使用此控件的模块的导入中。

在模板驱动的表单中使用


这里的一切都很简单,通过ngModel使用双向绑定,当模型更改时,我们将获得视图更改,反之亦然:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control [(ngModel)]="controlValue"></counter-control> ` }) class ParentComponent { controlValue = 10; } 

在反应形式中使用


如本文开头所述,通过FormBuilder创建反应形式的实例,以html 呈现并享受:

 import { Component, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; @Component({ selector: 'parent-component', template: ` <form [formGroup]="form"> <counter-control formControlName="counter"></counter-control> </form> ` }) class ParentComponent implements OnInit { form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ counter: 5 }); } } 

现在,这是带有我们自定义控件的成熟的反应形式,而所有机制的工作方式都与本机控件相同(我是否提到了多态性?)。 为了不加载当前文章,我们将在下一篇文章中讨论验证和显示自定义控件错误。

材料:

Pascal Precht中有关自定义控件的文章
码头的形式成角度。
有关rxjs的一系列文章

Source: https://habr.com/ru/post/zh-CN481424/


All Articles