Обновить значение свойства в директиве

#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() там я получаю родительский