#angular #angular2-directives
#angular #angular2-директивы
Вопрос:
Я пытаюсь создать свою собственную директиву для подсчета значения длины ввода.
-
при инициализации просмотра я добавляю в свою директиву атрибут maxlength со значением, отправленным в директиву -> ok
-
после инициализации просмотра я добавляю div перед своей директивой со значением 0/50 —> ok
У меня просто возникла проблема с обновлением значения длины при использовании моей клавиатуры (свойство обновляется, но не рендеринг). Не могли бы вы мне помочь, пожалуйста?
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);
}
}
вот как я использовал директиву:
<input type="text" [appInputmaxLength]="'5'" />
Спасибо вам за вашу помощь, я заблудился.
Комментарии:
1. Не могли бы вы также привести пример того, как вы используете эту директиву?
2. Готово, я отредактировал свой пост
Ответ №1:
Вот демонстрационная версия Stackblitz директивы
Я внес несколько изменений в ваш код, вот что я предлагаю:
- Измените свой
appInputMaxLength
тип на number - Используйте
Renderer2
API как можно чаще, чтобы обеспечить кросс-платформенную совместимость. - Используйте частное
div
свойство для хранения вашего div и обновите его позже, создайте его с помощьюthis.renderer.createElement('div')
- Используйте
this.renderer.insertBefore(this.el.nativeElement.parentNode, this.div, this.el.nativeElement.nextSibling)
, чтобы вставить его после хоста - Прослушайте изменения, используя
input
событие, и получите значение из события, затем получите его длину и обновитеdiv
- Вам не нужно сохранять
currentValue
переменную, просто получите длину из входного значения или события - Используйте
this.renderer.setProperty(this.div, 'innerText', ...);
для обновления текста вашего элемента div - Удалите элемент div, поскольку Angular не будет его отслеживать. Для этого вы не можете использовать,
this.renderer.removeChild(this.el.nativeElement.parent, this.div)
посколькуngOnDestroy
вызывается после удаления DOM, иparent
ссылка тогда будет равна null. Вам придется вызывать напрямуюthis.div.remove()
(смотрите Эту проблему на github).
Обновленный код директивы
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}`);
}
}
Используйте его следующим образом, вводя числовое значение:
<input type="text" [appInputMaxLength]="10">
Код директивы, совместимый с ngModel
Если вы хотите, чтобы ваша директива работала, когда ngModel
привязан к входным данным, и соответствующим образом обновлялась при изменении модели, вы можете получить путем внедрения хост ngModel
, а затем подписаться на его valueChange
наблюдаемый:
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}`);
}
}
Затем вы можете использовать свою директиву для ввода с ngModel
:
<input type="text" [appInputMaxLength]="10" [(ngModel)]="value">
Комментарии:
1. Ок, все работает хорошо, спасибо за совет по обнаружению всех изменений 🙂
2. @orphen92300, я обновил ответ и stackblitz, чтобы обработать случай, когда ваш ввод уничтожен (например, с помощью ngIf). Теперь div правильно удален
3. @orphen92300, это ответ на твой вопрос?
Ответ №2:
правильный способ — использовать установщик. Функция setter вызывается каждый раз, когда входные данные изменяются.
@Input() set appInputmaxLength(value:string){
// Your code here
console.log(value);
}
Пример можно найти здесь:https://angular.io/guide/structural-directives (Директива You не является структурной директивой, но она есть в этом примере)
Ответ №3:
попробуйте этот код, я переписал некоторые коды
import {
AfterViewInit,
Directive,
ElementRef,
HostListener,
Input,
OnInit,
Renderer2,
OnDestroy
} from '@angular/core';
@Directive({
selector: '[appInputmaxLength]'
})
export class InputmaxLengthDirective implements OnInit, AfterViewInit, OnDestroy {
@Input() appInputmaxLength: string;
private currentValue = 0;
countDiv: HTMLDivElement;
parent: any;
constructor(private el: ElementRef<HTMLInputElement>, private renderer: Renderer2) {}
@HostListener('keyup') isChange() {
const countNb = this.el.nativeElement.value.length 1;
if (countNb <= 1) {
this.currentValue = 0;
this.updateCount();
} else {
this.currentValue = countNb;
this.updateCount();
}
console.log('test: ', this.el.nativeElement.value.length 1);
}
ngOnInit() {
this.renderer.setAttribute(this.el.nativeElement, 'maxLength', this.appInputmaxLength);
}
ngOnDestroy() {
this.renderer.removeChild(this.parent, this.countDiv);
this.renderer.destroyNode(this.countDiv);
}
updateCount() {
this.countDiv.innerText = this.currentValue ' / ' this.appInputmaxLength;
}
ngAfterViewInit() {
this.countDiv = this.renderer.createElement('div');
this.parent = this.renderer.parentNode(this.el.nativeElement);
this.renderer.appendChild(parent, this.countDiv);
this.updateCount();
}
}
в этом коде я использую средство визуализации для создания элемента div и обновления его значения везде, где currentValue
происходят изменения.
Также вы должны уничтожить его при уничтожении директивы, чтобы избежать утечки памяти
Комментарии:
1. спасибо, что оно работает. Я изменил нажатие клавиши на нажатие клавиши вверх. У меня просто последняя проблема, div создается внутри ввода, а не после. Как я могу это сделать, пожалуйста?
2. пожалуйста, проверьте,
ngAfterViewInit()
там я получаю родительский