【问题标题】:Update propertie value in directive更新指令中的属性值
【发布时间】:2019-03-19 09:12:37
【问题描述】:

我正在尝试创建自己的指令来计算输入长度值。

  • 在视图初始化时,我在指令中添加了带有值的 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'" />

谢谢你的帮助,我迷路了。

【问题讨论】:

  • 您能否提供一个如何使用该指令的示例?
  • 完成我已经编辑了我的帖子

标签: angular angular2-directives


【解决方案1】:

Here is a Stackblitz demo 的指令

我对你的代码做了一些修改,这是我的建议:

  • 将您的 appInputMaxLength 类型更改为数字
  • 尽可能多地使用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),因为在删除 DOM 后会调用 ngOnDestroy,并且 parent 引用将为空。您必须直接致电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}`);
  }
}

像这样使用它,带有一个数字输入值:

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


与ngModel兼容的指令代码

如果您希望指令在 ngModel 绑定到输入时起作用,并在模型更改时相应更新,您可以通过注入主机 ngModel 然后订阅其 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}`);
  }
}

然后您可以在带有ngModel 的输入上使用您的指令:

<input type="text" [appInputMaxLength]="10" [(ngModel)]="value">

【讨论】:

  • 好的,一切都很好,谢谢您提供检测所有更改的提示:)
  • @orphen92300,我更新了答案和 stackblitz 以处理您的输入被破坏的情况(例如使用 ngIf)。现在 div 已正确删除
  • @orphen92300,这能回答你的问题吗?
【解决方案2】:

正确的方法是使用 setter。每次输入更改时都会调用 setter 函数。

    @Input() set appInputmaxLength(value:string){
    // Your code here
    console.log(value);
    }

可以在此处找到示例:https://angular.io/guide/structural-directives(您的指令不是结构指令,但在此示例中)

【讨论】:

    【解决方案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 发生变化的任何地方更新其值。

    另外你必须在指令被销毁时销毁它以避免内存泄漏

    【讨论】:

    • 感谢您的工作。我已经用 Key up 更改了 keydown。我只是有最后一个问题,div 是在输入内部而不是之后创建的。请问我该怎么做?
    • 请在ngAfterViewInit()那里查看我的父母
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-09
    • 2016-01-07
    • 2016-11-29
    • 2018-03-28
    相关资源
    最近更新 更多