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.
Here is a Stackblitz demo of the directive
I made a few modifications to your code to make, here is what I suggest:
appInputMaxLength type to numberRenderer2 API as much as you can bo be cross-platform compatible.div property to hold your div and update it later, create it with this.renderer.createElement('div')this.renderer.insertBefore(this.el.nativeElement.parentNode, this.div, this.el.nativeElement.nextSibling) to insert it after the hostinput event, and get the value from the event, then get its length and update the divcurrentValue variable, just get the length from the input's value or eventthis.renderer.setProperty(this.div, 'innerText', ...); to update the text of your div elementthis.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).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">
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">
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