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.
2 respuestas
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 { }
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:
Entonces, ¿cómo podemos establecer esta propiedad para los niños?
Tenemos a los niños dentro de nuestra variable contentChildren :
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:
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)
Preguntas relacionadas
Nuevas preguntas
angular
Preguntas sobre Angular (que no debe confundirse con AngularJS), el marco web de Google. Use esta etiqueta para preguntas angulares que no son específicas de una versión individual. Para el marco web anterior de AngularJS (1.x), use la etiqueta angularjs.