如何使用 ControlValueAccessor 在自定义输入中使用指令 Angular
How To Use A Directive In A Custom Input With ControlValueAccessor Angular
我一直在我的 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 dosn't work in my-input
这是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, ''));
}
}
有没有办法在my-input
组件中使用myDirective
?
提前致谢。
你不能直接,但你可以用指令查询做一些 Hack
根据 Angular 指令文档:
Decorator that marks a class as an Angular directive. You can define your own directives to attach custom behaviour to elements in the DOM.
装饰器是附加到 DOM 元素的行为,因此它会影响使用该指令的元素。
BUT(这是一个很大的 BUT 是有原因的 - 检查注释),您可以使用指令查询来破解此行为。
但是记住你必须定义特定的指令,这些指令只能用于特定的查询,不能用于任何DOM元素。
解决方案
解决方案依据如下:
I want to define a directive which works on an element children, and not on the element itself.
您可以使用 @ContentChildren 和 QueryList 来检查您是否有一些带有特定指令的 children。
例如,我有一个后台指令应用于输入 parent 和一个 CustomFormControlDirective 来查询 children:
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>
当然这个指令会将背景颜色应用到 parent div:
那么我们如何将这个 属性 设置为 children?
我们的 contentChildren 变量中有 children:
所以我们需要将所需的背景应用到我们的 children 元素,我们可以遍历查询结果并尝试应用样式:
[...]
_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;
});
}
}
[...]
备注
- 这是一个示例,请勿采用此实现方式as-is,您可以定义自己的方法或使用更好的方法,但请尝试理解 QueryList 选择器和 ContentChildren。
- 使用自定义指令获取 children 可能不是必需的,您可以直接使用 ReactiveForm 指令(AbstractControlDirective / FormControlDirective),但我认为它们不会让您访问 DOM因为它是私人的。
- 这些指令仅适用于 children,因此您可以选择更好的命名约定(例如 ApplyToControlBackgroundDirective)
你的指令有问题。注入一个 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 { }
我一直在我的 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 dosn't work in my-input
这是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, ''));
}
}
有没有办法在my-input
组件中使用myDirective
?
提前致谢。
你不能直接,但你可以用指令查询做一些 Hack
根据 Angular 指令文档:
Decorator that marks a class as an Angular directive. You can define your own directives to attach custom behaviour to elements in the DOM.
装饰器是附加到 DOM 元素的行为,因此它会影响使用该指令的元素。
BUT(这是一个很大的 BUT 是有原因的 - 检查注释),您可以使用指令查询来破解此行为。
但是记住你必须定义特定的指令,这些指令只能用于特定的查询,不能用于任何DOM元素。
解决方案
解决方案依据如下:
I want to define a directive which works on an element children, and not on the element itself.
您可以使用 @ContentChildren 和 QueryList 来检查您是否有一些带有特定指令的 children。
例如,我有一个后台指令应用于输入 parent 和一个 CustomFormControlDirective 来查询 children:
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>
当然这个指令会将背景颜色应用到 parent div:
那么我们如何将这个 属性 设置为 children?
我们的 contentChildren 变量中有 children:
所以我们需要将所需的背景应用到我们的 children 元素,我们可以遍历查询结果并尝试应用样式:
[...]
_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;
});
}
}
[...]
备注
- 这是一个示例,请勿采用此实现方式as-is,您可以定义自己的方法或使用更好的方法,但请尝试理解 QueryList 选择器和 ContentChildren。
- 使用自定义指令获取 children 可能不是必需的,您可以直接使用 ReactiveForm 指令(AbstractControlDirective / FormControlDirective),但我认为它们不会让您访问 DOM因为它是私人的。
- 这些指令仅适用于 children,因此您可以选择更好的命名约定(例如 ApplyToControlBackgroundDirective)
你的指令有问题。注入一个 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 { }