如何在 ANGULAR 8 中双向绑定自定义指令?

How to 2-way bind custom directive in ANGULAR 8?

摘要

问题/问题

  1. 如何从指令中清除 ruleSearchText 变量值,以便在 ngOnChanges
  2. 中再次触发更改
  3. 函数 clearInputField 清除值,但它不会触发 ngOnchanges 以及侦听基础组件中值更改的任何其他事件。

代码示例

<input
  #search
  name="search"
  type="text"
  class="form-control underlined"
  placeholder="Search Rule Name"
  [(ngModel)]="ruleSearchText"
  [appSearchbox]="ruleSearchText"
/>
import {
  Directive,
  ElementRef,
  Input,
  OnChanges,
  OnDestroy,
  Renderer2
} from '@angular/core';

/**
 * Directive to add CANCEL button functionality for input boxes.
 * Clicking cancel button will clear input box value.
 *
 * @export
 * @class SearchboxDirective
 * @implements {OnChanges}
 * @implements {OnDestroy}
 */
@Directive({
  selector: '[appSearchbox]',
})
export class SearchboxDirective implements OnChanges, OnDestroy {
  /**
   * Use inputs variable that holds value to bind with attribute.
   *
   * @example <input name="search" type="text" [(ngModel)]="searchTextVar" [appSearchbox]="searchTextVar"/>
   *
   * @memberof SearchboxDirective
   */
  @Input() public appSearchbox;

  public cancelContainer;
  public cancelStyle;
  public unlistener;

  /**
   * Creates an instance of SearchboxDirective.
   * @constructor
   * @param {ElementRef} el
   * @param {Renderer2} renderer
   * @memberof SearchboxDirective
   */
  public constructor(private el: ElementRef, private renderer: Renderer2) {
    this.cancelStyle = {
      top: '50%',
      right: '0.5rem',
      transform: 'translateY(-50%)',
      cursor: 'pointer',
    };
  }

  /**
   * Triggers when to ADD/REMOVE cancel icon to inputbox.
   *
   * @param {*} changes
   * @memberof SearchboxDirective
   */
  public ngOnChanges(changes) {
    if (
      changes.appSearchbox.currentValue === '' ||
      changes.appSearchbox.currentValue === undefined
    ) {
      this.toggleCancelIcon(false);
    } else if (changes.appSearchbox.currentValue !== '') {
      this.toggleCancelIcon(true);
    }
  }

  /**
   * Removes/cleans CLICK event listener of cancel icon.
   *
   * @memberof SearchboxDirective
   */
  public ngOnDestroy() {
    if (this.unlistener) {
      this.unlistener();
    }
  }

  /**
   * Show / Hide cancel icon.
   *
   * @param {true|false} state
   * @memberof SearchboxDirective
   */
  private toggleCancelIcon(state) {
    if (state === true) {
      const cancelBtn = this.renderer
        .parentNode(this.el.nativeElement)
        .querySelector('#inputCancelBtn');
      if (!cancelBtn) {
        this.cancelStyle['height'] = `${this.el.nativeElement.clientHeight}px`;
        this.renderer.setStyle(
          this.el.nativeElement,
          'padding-right',
          '2.5rem'
        );
        this.cancelContainer = this.renderer.createElement('div');
        this.renderer.setAttribute(
          this.cancelContainer,
          'id',
          'inputCancelBtn'
        );
        this.renderer.setAttribute(
          this.cancelContainer,
          'class',
          'd-inline-block position-absolute'
        );
        this.unlistener = this.renderer.listen(
          this.cancelContainer,
          'click',
          () => {
            this.cleatInputField();
          }
        );
        this.setRendererStyles(this.cancelContainer, this.cancelStyle);
        const cancelButton = this.renderer.createElement('span');
        this.renderer.setAttribute(
          cancelButton,
          'class',
          'icon icon-close-thin'
        );
        this.renderer.appendChild(this.cancelContainer, cancelButton);
        this.renderer.appendChild(
          this.renderer.parentNode(this.el.nativeElement),
          this.cancelContainer
        );
      }
    } else if (state === false) {
      const cancelBtn = this.renderer
        .parentNode(this.el.nativeElement)
        .querySelector('#inputCancelBtn');
      if (cancelBtn) {
        this.renderer.removeChild(
          this.renderer.parentNode(this.el.nativeElement),
          cancelBtn
        );
        this.renderer.setStyle(
          this.el.nativeElement,
          'padding-right',
          'init'
        );
      }
    }
  }

  /**
   * Loop through styles object and set given elements styles using Renderer2 setStyle method.
   *
   * @param {*} element
   * @param {Object} styles
   * @memberof SearchboxDirective
   */
  private setRendererStyles(element, styles) {
    for (const [key, value] of Object.entries(styles)) {
      this.renderer.setStyle(element, key, value);
    }
  }

  private clearInputField() {
    this.el.nativeElement.value = '';
    this.toggleCancelIcon(false);
  }
}


例子

您可以在指令中使用 output 将 'X' 事件表单指令传递给组件,如下所示:

<input
  #search
  name="search"
  type="text"
  class="form-control underlined"
  placeholder="Search Rule Name"
  [(ngModel)]="ruleSearchText"
  [appSearchbox]="ruleSearchText"
  (clearSearch)="clearText($event)"
/>

在指令 (appSearchbox) 中添加此方法以清除文本和输出装饰器:

@Output() clearSearch = new EventEmitter();
..
..
..

clear() {
   this.clearSearch.emit(true);
}

在父组件中添加:

clearText(status) {
   this.ruleSearchText = status ? '' : this.ruleSearchText;
}