1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

Dynamic Directive not applying, in reusable controller

Discussão em 'Angular' iniciado por Kathiravan S, Setembro 9, 2025.

  1. Kathiravan S

    Kathiravan S Guest

    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...

Compartilhe esta Página