Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update propertie value in directive

I'm trying to create my own directive to count input length value.

  • on view init I'm add with my directive the maxlength attribute with value send to the directive --> ok

  • on after view init I add a div before my directive with the count like 0/50 --> ok

I just have a problem for updating the length value when I use my keybord (the property is updated but not the render). Can you help me please?

import {
  AfterViewInit,
  Directive, ElementRef, HostListener, Input, OnInit, Renderer2
} from '@angular/core';

@Directive({
  selector: '[appInputmaxLength]'
})
export class InputmaxLengthDirective implements OnInit, AfterViewInit {
  @Input() appInputmaxLength: string;
  private currentValue = 0;

  constructor(
    private el: ElementRef,
    private renderer: Renderer2
  ) {}

  @HostListener('keydown') isChange() {
    let countNb = this.el.nativeElement.value.length + 1;
    if (countNb <= 1) {
      this.currentValue = 0;
    } else {
      this.currentValue = countNb;
    }

    console.log('test: ', this.el.nativeElement.value.length + 1);
  }

  ngOnInit() {
    this.renderer.setAttribute(this.el.nativeElement, 'maxLength', this.appInputmaxLength);
  }

  ngAfterViewInit() {
    const html = '<div>' + this.currentValue + ' / ' + this.appInputmaxLength + '</div>'
    const target = this.el;
    target.nativeElement.insertAdjacentHTML('afterEnd', html);
  }

}

this is how i used the directive:

<input type="text" [appInputmaxLength]="'5'" />

Thanks you for your help, I'm lost.

like image 756
orphen92300 Avatar asked Dec 18 '25 06:12

orphen92300


1 Answers

Here is a Stackblitz demo of the directive

I made a few modifications to your code to make, here is what I suggest:

  • Change your appInputMaxLength type to number
  • Use the Renderer2 API as much as you can bo be cross-platform compatible.
  • Use a private div property to hold your div and update it later, create it with this.renderer.createElement('div')
  • Use this.renderer.insertBefore(this.el.nativeElement.parentNode, this.div, this.el.nativeElement.nextSibling) to insert it after the host
  • Listen to changes using the input event, and get the value from the event, then get its length and update the div
  • You don't need to keep a currentValue variable, just get the length from the input's value or event
  • Use this.renderer.setProperty(this.div, 'innerText', ...); to update the text of your div element
  • Remove the div element since Angular won't keep track of it. For this you cannot use this.renderer.removeChild(this.el.nativeElement.parent, this.div) since ngOnDestroy is called after the DOM has been removed and the parent reference will then be null. You have to directly call this.div.remove() (see this github issue).

Updated directive code

import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnInit, Renderer2, OnDestroy } from '@angular/core';

@Directive({
  selector: '[appInputMaxLength]'
})
export class InputMaxLengthDirective implements OnInit, AfterViewInit, OnDestroy {
  @Input() appInputMaxLength: number;
  private div: HTMLDivElement;

  constructor(private el: ElementRef, private renderer: Renderer2) {}

  @HostListener('input', ['$event']) onChange(event) {
    this.update(event.target.value.length);
  }

  ngOnInit() {
    this.renderer.setAttribute(this.el.nativeElement, 'maxLength', this.appInputMaxLength.toString());
  }

  ngOnDestroy() {
    if (this.div) {
      this.div.remove();
    }
  }

  ngAfterViewInit() {
    this.div = this.renderer.createElement('div');
    this.renderer.insertBefore(this.el.nativeElement.parentNode, this.div, this.el.nativeElement.nextSibling);
    this.update(this.el.nativeElement.value.length);
  }

  private update(length: number) {
    this.renderer.setProperty(this.div, 'innerText', `${length} / ${this.appInputMaxLength}`);
  }
}

Use it like this, with a number input value:

<input type="text" [appInputMaxLength]="10">


Directive code compatible with ngModel

If you want your directive to work when an ngModel is bound to the input and update accordingly if the model changes, you can get by injection the host ngModel and then subscribe to its valueChange observable:

import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnInit, Renderer2, Optional, OnDestroy } from '@angular/core';
import { NgModel } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Directive({
  selector: '[appInputMaxLength]'
})
export class InputMaxLengthDirective implements OnInit, AfterViewInit, OnDestroy {
  @Input() appInputMaxLength: number;
  private div: HTMLDivElement;
  private destroyed$ = new Subject();

  constructor(private el: ElementRef, private renderer: Renderer2, @Optional() private ngModel: NgModel) {}

  @HostListener('input', ['$event']) onChange(event) {
    if (!this.ngModel) {
      this.update(event.target.value.length);
    }
  }

  ngOnInit() {
    this.renderer.setAttribute(this.el.nativeElement, 'maxLength', this.appInputMaxLength.toString());
    if (this.ngModel) {
      this.ngModel.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(value => {
        this.update(value.length);
      })
    }
  }

  ngAfterViewInit() {
    this.div = this.renderer.createElement('div');
    this.renderer.insertBefore(this.el.nativeElement.parentNode, this.div, this.el.nativeElement.nextSibling);
    this.update(this.el.nativeElement.value.length);
  }

  ngOnDestroy() {
    this.destroyed$.next();
    this.destroyed$.complete();
    if (this.div) {
      this.div.remove();
    }
  }

  private update(length: number) {
    this.renderer.setProperty(this.div, 'innerText', `${length} / ${this.appInputMaxLength}`);
  }
}

Then you can use your directive on an input with an ngModel:

<input type="text" [appInputMaxLength]="10" [(ngModel)]="value">
like image 132
jo_va Avatar answered Dec 19 '25 20:12

jo_va