简体   繁体   中英

How To Use A Directive In A Custom Input With ControlValueAccessor Angular

I've been create a simple custom input component in my angular app use ControlValueAccessor . So, when I want to create a form input element, I don't have to call <input /> , only call <my-input> .

I have a problem, when I use <input /> , I can use myDirective . For example:

<input type="text" class="form-control" formControlName="name" myDirective />

But, when I use my-input , then I can't use myDirective . For example:

<my-input formControlName="name" myDirective></my-input>

myDirective dosn't work in my-input

This is my-input component use ControlValueAccessor code:

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;
  }
}

Updated: myDirective code:

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, ''));
    }
}

Is there a way for myDirective to be used in the my-input component?

Thanks in advance.

There're a problem with your directive. Inject a NgControl and control this 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, ''));
    }
}

You can see in stackblitz

NOTE: Don't forget include in the module declarations

@NgModule({
  imports:      [ BrowserModule, FormsModule,ReactiveFormsModule ],
  declarations: [ AppComponent, MyInputComponent,MyDirective ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

You can't directly but you can do some Hack with directives queries

According to the Angular Directives documentation:

Decorator that marks a class as an Angular directive. You can define your own directives to attach custom behaviour to elements in the DOM.

a decorator is a behaviour attached to an element to the DOM, so it will affect the element wich use the directive.

BUT (and is a big BUT for a reason - check notes), you can hack this behaviour using directive queries.

BUT remember you must define specific directives which will work only with specific queries and cannot be used for any DOM element.

The solution

The solution is based on the following:

I want to define a directive which works on an element children, and not on the element itself.

You can use @ContentChildren and QueryList to check if you have some children with a specific directive.

For example, I have a background directive applied to input parent and a CustomFormControlDirective to query the 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>

Of course this directive will apply the bg color to the parent div:

div 带红色 bg]

So how can we set this property to the children?

We have the children inside our contentChildren variable:

控制台日志

So we need to apply the desired background to our children element, we can loop trough the query results and try to apply the style:

  [...]
  _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;
      });
    }
  }
  [...]

And it will apply the style to childrens: 有红色 bg 的孩子

also if there are more than 1 children: 更多花哨的红色bgs

Notes

  • this is an example, don't take this implementation as-is, you might define your own method or use a better one, but try to understand the QueryList selectors and ContentChildren.
  • using a custom directive to fetch childrens might not be necessary, you might use ReactiveForm directives directly (AbstractControlDirective / FormControlDirective) but I don't think they will let you access the DOM as it is private.
  • these directives will work only with children, so you might choose a better naming convention (eg ApplyToControlBackgroundDirective)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM