Reputation: 3613
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 themy-input
component?
Thanks in advance.
Upvotes: 6
Views: 7459
Reputation: 57929
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 { }
Upvotes: 4
Reputation: 72
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 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:
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:
also if there are more than 1 children:
Upvotes: 2