He creado un componente simple input personalizado en mi uso angular de la aplicación ControlValueAccessor. Entonces, cuando quiero crear un elemento de formulario input, no tengo que llamar a <input />, solo llamar a <my-input>.

Tengo un problema, cuando uso <input />, puedo usar myDirective. Por ejemplo:

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

Pero, cuando uso my-input, entonces no puedo usar myDirective. Por ejemplo:

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

myDirective no funciona en my-input

Este es el componente my-input uso del código 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;
  }
}

Actualizado: código 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, ''));
    }
}

¿Hay alguna forma de utilizar myDirective en el componente my-input?

Gracias de antemano.

1
Titus Sutio Fanpula 19 feb. 2020 a las 11:18

2 respuestas

La mejor respuesta

Hay un problema con su directiva. Inyecte un NgControl y controle este 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, ''));
    }
}

Puede ver en stackblitz

NOTA: No olvide incluir en las declaraciones del módulo

@NgModule({
  imports:      [ BrowserModule, FormsModule,ReactiveFormsModule ],
  declarations: [ AppComponent, MyInputComponent,MyDirective ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
1
Eliseo 19 feb. 2020 a las 09:40

No puedes directamente pero puedes hacer algunas consultas de Hack con directivas

De acuerdo con la documentación de las directivas angulares:

Decorador que marca una clase como una directiva angular. Puede definir sus propias directivas para adjuntar un comportamiento personalizado a elementos en el DOM.

Un decorador es un comportamiento asociado a un elemento del DOM, por lo que afectará al elemento que use la directiva.

PERO (y es un gran PERO por una razón - verifique las notas), puede hackear este comportamiento usando consultas directivas.

PERO recuerde debe definir directivas específicas que funcionarán solo con consultas específicas y no se pueden usar para ningún elemento DOM.

La solución

La solución se basa en lo siguiente:

Quiero definir una directiva que funcione en un elemento hijos, y no en el elemento en sí.

Puede usar @ContentChildren y QueryList para verificar si tiene algunos hijos con una directiva específica.

Por ejemplo, tengo una directiva de fondo aplicada a la entrada principal y una CustomFormControlDirective para consultar a los hijos:

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>

Por supuesto, esta directiva aplicará el color bg al div padre:

Div with red bg]

Entonces, ¿cómo podemos establecer esta propiedad para los niños?

Tenemos a los niños dentro de nuestra variable contentChildren :

Console log

Por lo tanto, debemos aplicar el fondo deseado a nuestro elemento secundario, podemos recorrer los resultados de la consulta e intentar aplicar el estilo:

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

Y aplicará el estilo a los niños: Niños con bg rojo

También si hay más de 1 niño: Más elegantes bgs rojos

Notas

  • este es un ejemplo, no tome esta implementación tal como está, podría definir su propio método o utilizar uno mejor, pero intente comprender los selectores de QueryList y ContentChildren.
  • Es posible que no sea necesario usar una directiva personalizada para buscar niños, puede usar las directivas ReactiveForm directamente (AbstractControlDirective / FormControlDirective) pero no creo que le permitan acceder al DOM ya que es privado.
  • estas directivas solo funcionarán con niños, por lo que puede elegir una convención de nomenclatura mejor (por ejemplo, ApplyToControlBackgroundDirective)
1
nicola_castellani 19 feb. 2020 a las 09:33