如何使用 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;
      });
    }
  }
  [...]

并将样式应用于 childrens:

如果超过 1 个 children:

备注

  • 这是一个示例,请勿采用此实现方式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 { }