I'm trying to create a reusable controller for textbox, textarea, dropdown. etc, there are plenty of directives in my project. so I tried to render it dynamically validation-mask.directive.ts: @Directive({ selector: '[maskNumber]', standalone: false }) export class NumberMask { private el: any; @Output() ngModelChange = new EventEmitter(); constructor(el: ElementRef, public render: Renderer2) { this.el = el.nativeElement } @HostListener('input', ['$event.target.value']) onInput(event: any) { if (!!event) { var val = event.replace(/[^0-9]+/g, ''); if (event != val) { this.el.value = val; this.ngModelChange.emit(val); } } } } @Directive({ selector: '[maskTestInterpretationRange]', standalone: false }) export class TestInterpretationRange { private el: any; private messageBundle: {}; @Output() ngModelChange = new EventEmitter(); constructor(el: ElementRef, public toast: ToastMessage,public messageBundleService: MessageBundleService) { this.el = el.nativeElement, this.messageBundle = {}; this.messageBundle = this.messageBundleService.MsgBundle; } @HostListener('blur', ['$event.target.value']) onInput(event: any) { if (event) { } var val = event.match(/^(?:[><])?(?:\=)?\d+(?:\.\d+)?$/); if (!val) { this.el.value = ""; this.ngModelChange.emit(""); this.toast.show(this.messageBundle['invalidrange'], "X", 3000, "error", 'ltr'); } } } Existing code: <mat-form-field> <input matInput name="username" [(ngModel)]="sampleTxt" maskNumber autocomplete="off" placeholder="Username" autocomplete="off"> </mat-form-field> New code: <lims-input [(ngModel)]="sampleTxt" [placeholder]="'username'" [inputAttrs]="{autocomplete: 'off', maskNumber:true}"></lims-input> lims-input.component.html: <mat-form-field class="mat-overwrite-input w-100" [class]="staticClass" [ngClass]="dynamicClass" [ngClass]="{'hint-hide' : hintHide }"> <mat-label>{{placeholder | translate}}</mat-label> <input matInput #inputRef [id]="id" [type]="type" [disabled]="disabled" [required]="required" [name]="name" [min]="min" [max]="max" [step]="step" [attr.maxlength]="maxlength" [attr.minlength]="minlength" [(ngModel)]="value" [tabindex]="tabindex" [readonly]="readonly" (input)="onInputChange($event)" [matTooltip]="value" [matTooltipDisabled]="tooltipDisabled" [matTooltipPosition]="tooltipPosition" (blur)="onBlur($event)" (keyup)="onKeyup($event)" (keydown)="onKeydown($event)" /> @if (closeIcon && value) { <button matSuffix mat-icon-button aria-label="Clear" (click)="closeBtnClick()"> <mat-icon>close</mat-icon> </button> } </mat-form-field> lims-input.component.ts: import { NgClass } from '@angular/common'; import { Component, ElementRef, EventEmitter, forwardRef, Input, Output, Renderer2, ViewChild } from '@angular/core'; import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslateModule } from 'src/app/shared/modules/translate/translate.module'; @Component({ selector: 'lims-input', imports: [NgClass, MatInputModule, MatTooltipModule, MatButtonModule, MatIconModule, TranslateModule, MatFormFieldModule, FormsModule, ReactiveFormsModule], templateUrl: './lims-input.component.html', styleUrl: './lims-input.component.scss', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LimsInputComponent), // Register this component as a ControlValueAccessor multi: true } ] }) export class LimsInputComponent { @ViewChild('inputRef', { static: true }) private _inputRef!: ElementRef; @Input() inputAttrs: { [key: string]: any } = {}; @Input() min: string | number = ''; @Input() max: string | number = ''; @Input() step: string | number = ''; @Input() id: string = ''; @Input() staticClass: string = ''; @Input() dynamicClass = {}; @Input() type: string = 'text'; @Input() placeholder: string = ''; @Input() name: string = ''; @Input() closeIcon: boolean = false; @Input() disabled: boolean = false; @Input() required: boolean = false; @Input() readonly: boolean = false; @Input() maxlength: number = 50; @Input() minlength: number | null = null; @Input() tooltipPosition: 'above' | 'below' | 'left' | 'right' = 'below'; @Input() tooltipDisabled: boolean = false; @Input() tabindex: string = ''; @Input() hintHide: boolean = false; @Output() valueChange = new EventEmitter<string>(); @Output() closeClick = new EventEmitter<null>(); @Output() blur = new EventEmitter<FocusEvent>(); @Output() keyup = new EventEmitter<KeyboardEvent>(); @Output() keypress = new EventEmitter<KeyboardEvent>(); @Output() keydown = new EventEmitter<KeyboardEvent>(); @Output() ngModelChange = new EventEmitter<any>(); @Output() inputRefEmitter = new EventEmitter<ElementRef>(); // ControlValueAccessor specific properties private _internalValue: string = ''; // Internal value to hold the model's state private onChange: (value: string) => void = () => { }; // Callback for value changes private onTouched: () => void = () => { }; // Callback for touch events // Getter for the native HTML input element get nativeInputElement(): HTMLInputElement { return this._inputRef.nativeElement; } // Getter/Setter for the component's value, used by ControlValueAccessor get value(): string { return this._internalValue || ''; } set value(val: string) { if (val !== this._internalValue) { this._internalValue = val; this.onChange(val); // Notify Angular forms of the change this.valueChange.emit(val); // Also emit to existing valueChange output // Note: ngModelChange output is handled by ControlValueAccessor internally // when you use [(ngModel)] on the host component. // If you still need a separate ngModelChange output for other reasons, // you can keep it and emit here, but it's often redundant with CVA. } else if (!this._internalValue) { this._internalValue = ''; } } constructor(private renderer: Renderer2) { } ngAfterViewInit(): void { if (this._inputRef) { this.inputRefEmitter.emit(this._inputRef); // Dynamically apply other attributes if (this.inputAttrs) { Object.entries(this.inputAttrs).forEach(([key, value]) => { if (value === true) { this.renderer.setAttribute(this._inputRef.nativeElement, key, ''); } else if (value !== false && value != null) { this.renderer.setAttribute(this._inputRef.nativeElement, key, String(value)); } }); } } } closeBtnClick(): void { this.value = ''; this.closeClick.emit(); } // --- ControlValueAccessor methods --- // Writes a new value to the element from the forms API writeValue(obj: string): void { this._internalValue = obj || ''; // Update the native input element's value directly if it's already rendered if (this._inputRef) { this.renderer.setProperty(this._inputRef.nativeElement, 'value', obj); } } // Registers a callback function to be called when the control's value changes registerOnChange(fn: (value: string) => void): void { this.onChange = fn; } // Registers a callback function to be called when the control is touched registerOnTouched(fn: () => void): void { this.onTouched = fn; } // Sets the disabled state of the control setDisabledState?(isDisabled: boolean): void { this.disabled = isDisabled; // Update the disabled input property this.renderer.setProperty(this._inputRef.nativeElement, 'disabled', isDisabled); } // --- Event handlers for native input --- onBlur(event: FocusEvent) { this.blur.emit(event); this.onTouched(); // Notify forms API that the control has been touched } onKeyup(event: KeyboardEvent) { this.keyup.emit(event); } onKeypress(event: KeyboardEvent) { this.keypress.emit(event); } onKeydown(event: KeyboardEvent) { this.keydown.emit(event); } onInputChange(event: Event) { const newValue = (event.target as HTMLInputElement).value; this.value = newValue; // Use the setter to update internal value and notify forms API } } I tried to render dynamically [inputAttrs]="{autocomplete: 'off', maskNumber:true}" as directive to controller by ngAfterViewInit, when I inspect it, maskNumber and autocomplete is showing inside the controller, but directive functionality is not working, when I debug, directive code is not working there are plenty of directives in our project, we should apply in textfield control and textarea control. how to achieve this. Continue reading...