• Angular写一个Form组件-TagInput


    前端开发少不了和表单打交道; Angular中, 提供了强大的表单的支持, 响应式表单(Reactive Form)模板驱动的表单(Template-driven Form) 的双向数据流给我们的开发带来了极大的便利; 借助angular, 我们除了可以使用html原生的输入控件, 也可以自定表单输入组件, 和用户更好的交互. 本文以 TagInput 组件为例, 说明在Angular中如何自定义表单组件;

    可以先看下最终效果

    github Page在线演示

    ControlValueAccessor

    自定义表单组件第一步, 实现ControlValueAccessor接口

    接口定义如下:

    ControlValueAccessor 接口声明
    export declare interface ControlValueAccessor {
        /**
         * @description
         * Writes a new value to the element.
         *
         * This method is called by the forms API to write to the view when programmatic
         * changes from model to view are requested.
         *
         * @usageNotes
         * ### Write a value to the element
         *
         * The following example writes a value to the native DOM element.
         *
         * ```ts
         * writeValue(value: any): void {
         *   this._renderer.setProperty(this._elementRef.nativeElement, 'value', value);
         * }
         * ```
         *
         * @param obj The new value for the element
         */
        writeValue(obj: any): void;
        /**
         * @description
         * Registers a callback function that is called when the control's value
         * changes in the UI.
         *
         * This method is called by the forms API on initialization to update the form
         * model when values propagate from the view to the model.
         *
         * When implementing the `registerOnChange` method in your own value accessor,
         * save the given function so your class calls it at the appropriate time.
         *
         * @usageNotes
         * ### Store the change function
         *
         * The following example stores the provided function as an internal method.
         *
         * ```ts
         * registerOnChange(fn: (_: any) => void): void {
         *   this._onChange = fn;
         * }
         * ```
         *
         * When the value changes in the UI, call the registered
         * function to allow the forms API to update itself:
         *
         * ```ts
         * host: {
         *    '(change)': '_onChange($event.target.value)'
         * }
         * ```
         *
         * @param fn The callback function to register
         */
        registerOnChange(fn: any): void;
        /**
         * @description
         * Registers a callback function that is called by the forms API on initialization
         * to update the form model on blur.
         *
         * When implementing `registerOnTouched` in your own value accessor, save the given
         * function so your class calls it when the control should be considered
         * blurred or "touched".
         *
         * @usageNotes
         * ### Store the callback function
         *
         * The following example stores the provided function as an internal method.
         *
         * ```ts
         * registerOnTouched(fn: any): void {
         *   this._onTouched = fn;
         * }
         * ```
         *
         * On blur (or equivalent), your class should call the registered function to allow
         * the forms API to update itself:
         *
         * ```ts
         * host: {
         *    '(blur)': '_onTouched()'
         * }
         * ```
         *
         * @param fn The callback function to register
         */
        registerOnTouched(fn: any): void;
        /**
         * @description
         * Function that is called by the forms API when the control status changes to
         * or from 'DISABLED'. Depending on the status, it enables or disables the
         * appropriate DOM element.
         *
         * @usageNotes
         * The following is an example of writing the disabled property to a native DOM element:
         *
         * ```ts
         * setDisabledState(isDisabled: boolean): void {
         *   this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
         * }
         * ```
         *
         * @param isDisabled The disabled status to set on the element
         */
        setDisabledState?(isDisabled: boolean): void;
    }
    

    这个接口包含了下面这些方法

    writeValue(obj: any): void;
    registerOnChange(fn: any): void;
    registerOnTouched(fn: any): void;
    setDisabledState?(isDisabled: boolean): void;
    
    • writeValue(obj: any): void; 表单值发生改变时Angular会调用这个方法给我们的表单组件赋值
    • registerOnChange(fn: any): void; Angular调用这个函数给我们的自己写的组件传递一个onChange方法, 调用这个方法, 会更新表单中的值
    • registerOnTouched(fn: any): void; Angular通过这个方法给我们在组件传递一个onTouch方法, 在我们的组件中调用onTouch会更新表单的 touched 字段

    注入 NG_VALUE_ACCESSOR

    除了实现 ControlValueAccessor 接口外, 我们自定义的表单组件还需要提供一个 token 为 NG_VALUE_ACCESSOR 的注入, 像下面这样

    import {
      forwardRef,
      OnInit,
    } from '@angular/core';
    import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
    
    @Component({
      selector: 'cti-tag-input',
      templateUrl: './tag-input.component.html',
      styleUrls: ['./tag-input.component.less'],
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => TagInputComponent),
          multi: true,
        },
      ],
    })
    export class TagInputComponent implements OnInit, ControlValueAccessor {
    }
    

    实例: TagInput 组件

    了解了上面的内容, 就可以开始编写 TagInput 组件了;

    组件的html模板
    <div [class.disabled]='disabled'
         class="tag-input-wrapper">
      <div class="tag-list">
        <span class="tag"
              *ngFor="let tag of tags">
          {{tag}}
        </span>
      </div>
      <div>
        <ng-container *ngIf="!isInputting">
          <button class="btn-add-tag"
                  (click)="onClick()">新增标签</button>
        </ng-container>
        <ng-container *ngIf="isInputting">
          <input type="text"
                 #tagInputEl
                 (keydown)="onKeyDown($event)"
                 [(ngModel)]="tagInput"
                 (blur)="onBlur()">
        </ng-container>
      </div>
    </div>
    
    

    TagInputComponent中需要定义如下字段

    // 文本输入框, 用来获取用户输入的标签的, 拿到这个可以在适当的时机对输入框进行 focus 操作
    @ViewChild('tagInputEl', { read: ElementRef })tagInputEl: ElementRef<HTMLInputElement>;
    tags: string[] = [];
    // 指示表单组件是否处于禁用状态
    disabled = false;
    // 保存用户输入的文字
    tagInput = '';
    // 当前是否正在输入
    isInputting = false;
    
    private _onChange = (_: string[]) => {};
    private _onTouch = () => {};
    

    实现ControlValueAccessor

    writeValue(obj: any): void {
    if (obj instanceof Array && obj.every((x) => typeof x === 'string')) {
        this.tags = obj;
    }
    }
    registerOnChange(fn: any): void {
    this._onChange = fn;
    }
    registerOnTouched(fn: any): void {
    this._onTouch = fn;
    }
    setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
    }
    

    在适当的时机调用 Angular 传递给我们的 _onChange, _onTouch 方法, 更新表单值

    onKeyDown(event: KeyboardEvent) {
        // 回车键键值: 13
        if (event.key.toLowerCase() === 'enter' || event.key === ',') {
            this.emitTags();
        }
    }
    
    onBlur = () => {
        this.emitTags();
    };
    
    onClick() {
        this.isInputting = !this.isInputting;
        let timer = setTimeout(() => {
            this.tagInputEl.nativeElement.focus();
            clearTimeout(timer);
            timer = undefined;
        }, 20);
    }
    
    private emitTags() {
        if (!this.tags.includes(this.tagInput) && this.tagInput) {
            this.tags.push(this.tagInput);
            this._onChange(this.tags);
        }
        this.tagInput = '';
        this.isInputting = false;
        this._onTouch();
    }
    

    查看完整的 TagComponent 代码

    最终效果

    查看完整代码

    CustomTagInput

  • 相关阅读:
    time fly
    小论文初稿终于完成
    leetcode之Length of Last Word
    static关键字
    参数传递
    this关键字
    面向对象有三大特征
    空指针异常
    变量按数据类型分为
    构造方法
  • 原文地址:https://www.cnblogs.com/Laggage/p/14159201.html
Copyright © 2020-2023  润新知