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.
If we also specify an input property in our directive's class using the same value as the selector property value we can input data into our directive using the attribute on the host element. This instructs Angular that the appBtnGrow class member property is now an input that can receive a value from the host element.
directive. ts file in src/app folder and add the code snippet below. Here, we are importing Directive and ElementRef from Angular core. The Directive provides the functionality of @Directive decorator in which we provide its property selector to appHighLight so that we can use this selector anywhere in the application.
Creating a custom directive is easy. Just create a new class and decorate it with the @Directive decorator. We need to make sure that the directive is declared in the corresponding (app-) module before we can use it. If you are using the angular-cli this should be done automatically.
Use the @Directive decorator in the class. Set the value of the selector property in the @directive decorator function. The directive will be used, using the selector value on the elements. In the constructor of the class, inject ElementRef and the renderer object.
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 { }
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With