如何使用 ControlValueAccessor Angular 在自定义输入中使用指令
Posted
技术标签:
【中文标题】如何使用 ControlValueAccessor Angular 在自定义输入中使用指令【英文标题】:How To Use A Directive In A Custom Input With ControlValueAccessor Angular 【发布时间】:2020-06-03 08:26:03 【问题描述】:我已经在我的 Angular 应用程序中使用 ControlValueAccessor
创建了一个简单的自定义 input
组件。所以,当我想创建一个表单input
元素时,我不必调用<input />
,只需调用<my-input>
即可。
我有一个问题,当我使用<input />
时,我可以使用myDirective
。例如:
<input type="text" class="form-control" formControlName="name" myDirective />
但是,当我使用my-input
时,我不能使用myDirective
。例如:
<my-input formControlName="name" myDirective></my-input>
myDirective 在我的输入中不起作用
这是my-input
组件使用ControlValueAccessor
代码:
import Component, forwardRef from '@angular/core';
import NG_VALUE_ACCESSOR, ControlValueAccessor from '@angular/forms';
@Component(
selector: 'my-input',
templateUrl: './my-input.component.html',
styleUrls: ['./my-input.component.scss'],
providers: [
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyInputComponent ),
multi: true
]
)
export class MyInputComponent implements ControlValueAccessor
onChange: () => void;
onTouched: () => void;
value: string;
writeValue(value: string): void
this.value = value ? value : '';
registerOnChange(fn: any): void
this.onChange = fn;
registerOnTouched(fn: any): void
this.onTouched = fn;
更新: myDirective
代码:
import Directive, HostListener from '@angular/core';
import FormControlName from '@angular/forms';
@Directive(
selector: '[myDirective]'
)
export class MyDirective
constructor(private formControlName: FormControlName)
@HostListener('input', ['$event'])
onInputChange()
this.formControlName.control.setValue(this.formControlName.value.replace(/[^0-9]/g, ''));
myDirective
有没有办法在my-input
组件中使用?
提前致谢。
【问题讨论】:
发布myDirective
实施
我之前被添加到我的问题中:<my-input formControlName="name" myDirective></my-input>
【参考方案1】:
您的指令有问题。注入一个NgControl 并控制这个ngControl
export class MyDirective
constructor(private control: NgControl) //<--inject NgControl
@HostListener('input', ['$event'])
onInputChange()
this.control.control.setValue(this.control.value.replace(/[^0-9]/g, ''));
你可以在stackblitz看到
注意:不要忘记包含在模块声明中
@NgModule(
imports: [ BrowserModule, FormsModule,ReactiveFormsModule ],
declarations: [ AppComponent, MyInputComponent,MyDirective ],
bootstrap: [ AppComponent ]
)
export class AppModule
【讨论】:
感谢您的回复,非常感谢,但我使用的是formGroup
和formControlName
。
Titus,该指令应用于 NgControl(NgControl 是带有 [(NgModel)]、[formControl] or formControlName 的输入)。在 stackblitz 中,我使用 formControl 来不创建 formGroup,但如果您有 formGroup,该指令也可以使用 works
因为我一直在尝试上面的代码,并将 myDirective
放入 my-input
选择器:<my-input formControlName="name" myDirective></my-input>
但它并没有改变值。但我敢肯定,从你的代码来看,它是有效的,这就是你回答我的问题的原因,我明白了,它是有效的。但在我的,使用formGroup
和formControlName
,它不能改变值。但感谢您的回复。提前致谢。
我使用 FormGroup 更新了 stackblitz。更改自定义表单控件中的值时是否忘记调用 onChange(value)?
当然,这就是我在上面的问题中所说的原因。请检查一下。它在input
中工作,但不在my-input
中。【参考方案2】:
您不能直接使用,但可以使用指令查询进行一些 Hack
根据 Angular Directives 文档:
将类标记为 Angular 指令的装饰器。您可以定义自己的指令来将自定义行为附加到 DOM 中的元素。
装饰器是附加到 DOM 元素的行为,因此它会影响使用该指令的元素。
但是(出于某种原因,这是一个很大的 BUT - 检查说明),您可以使用指令查询来破解此行为。
但是记住你必须定义特定的指令,这些指令只适用于特定的查询,不能用于任何 DOM 元素。
解决方案
解决方案基于以下几点:
我想定义一个适用于子元素而非元素本身的指令。
您可以使用@ContentChildren 和 QueryList 来检查您是否有一些具有特定指令的孩子。
例如,我有一个背景指令应用于输入父级和一个 CustomFormControlDirective 来查询子级:
import
Directive,
ElementRef,
Input,
ContentChildren,
ViewChildren,
QueryList
from "@angular/core";
@Directive(
selector: "[customformcontrol]"
)
export class CustomFormControlDirective
constructor(public elementRef: ElementRef)
@Directive(
selector: "[backgroundColor]",
queries:
contentChildren: new ContentChildren(CustomFormControlDirective),
viewChildren: new ViewChildren(CustomFormControlDirective)
)
export class BackgroundColorDirective
@Input()
set backgroundColor(color: string)
this.elementRef.nativeElement.style.backgroundColor = color;
@ContentChildren(CustomFormControlDirective, descendants: true )
contentChildren: QueryList<CustomFormControlDirective>;
viewChildren: QueryList<CustomFormControlDirective>;
constructor(private elementRef: ElementRef)
ngAfterContentInit()
// contentChildren is set
console.log("%o", this.contentChildren);
[...]
<div backgroundColor="red">
<input customformcontrol />
</div>
当然,这个指令会将 bg 颜色应用到父 div:
那么我们如何设置这个属性给孩子呢?
我们的 contentChildren 变量中有孩子:
所以我们需要为子元素应用所需的背景,我们可以遍历查询结果并尝试应用样式:
[...]
_backgroundColor = null;
@Input()
set backgroundColor(color: string)
/// save the bg color instead of apply style
this._backgroundColor = color;
ngAfterContentInit()
if (this.contentChildren.length)
/// then loop through childrens to apply style
this.contentChildren.forEach(customFormControl =>
customFormControl.elementRef.nativeElement.style.backgroundColor = this._backgroundColor;
);
[...]
它会将样式应用于儿童:
如果有超过 1 个孩子:
注意事项
这是一个示例,请勿按原样执行此实现,您可以定义自己的方法或使用更好的方法,但请尝试了解 QueryList 选择器和 ContentChildren。 可能不需要使用自定义指令来获取子项,您可以直接使用 ReactiveForm 指令(AbstractControlDirective / FormControlDirective),但我认为它们不会让您访问 DOM,因为它是私有的。 这些指令仅适用于儿童,因此您可以选择更好的命名约定(例如 ApplyToControlBackgroundDirective)【讨论】:
请记住,此解决方案适用于任意嵌套元素,不仅适用于 Reactive Forms 元素以上是关于如何使用 ControlValueAccessor Angular 在自定义输入中使用指令的主要内容,如果未能解决你的问题,请参考以下文章
嵌套 ControlValueAccessor 的角度验证状态未在父级中正确传播,如何实现此验证?
在 Angular 中使用 ControlValueAccessor 继承验证
带有 ControlValueAccessor 测试的 Angular 2(4) 组件
带有 ControlValueAccessor 和 formControlName 的 Angular Material Datepicker [重复]