浅析Angular19 自定义表单控件

yizhihongxing

下面我将为你详细讲解“浅析Angular19自定义表单控件”的完整攻略。如果您是Angular开发者,那么您一定知道表单是Web应用程序中至关重要的一部分。Angular提供了很多内置的表单控件,例如文本框、下拉框、单选框等。但是,在某些情况下,内置控件可能无法满足我们的需求。因此,我们需要自定义表单控件。下面是自定义表单控件的完整攻略:

1. 创建自定义表单控件

创建自定义表单控件需要创建一个组件,并实现controlvalueaccessor接口。接口是一个用于控制表单控件值的方法集合。

控件本身应该继承AbstractControl类以实现表单校验。使用@HostBinding绑定DOM元素。具体的示例如下:

import { Component, Input, Output, forwardRef, EventEmitter, OnInit, 
HostBinding } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, AbstractControl } 
from '@angular/forms';

@Component({
  selector: 'app-custom-control',
  template: '<input [(ngModel)]="value">',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomControlComponent),
      multi: true
    }
  ]
})
export class CustomControlComponent implements ControlValueAccessor {
  @Input() placeholder: string;
  @Output() customValueChange = new EventEmitter();
  value: any;

  onTouched: () => void = () => { };
  onChange: (_: any) => void = () => { };

  writeValue(value: any) {
    this.value = value
  }
  registerOnChange(fn: any) {
    this.onChange = fn;
  }
  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }

  onValueChange() {
    this.customValueChange.emit(this.value);
    this.onChange(this.value);
    this.onTouched();
  }

  setDisabledState(isDisabled: boolean) {
    // 写你自己的代码
  }
}

上面代码中,CustomControlComponent 组件是一个基本的输入框,它通过实现ControlValueAccessor接口来控制表单控件的值,通过监听输入框的变化,通知 onChange,onTouched两个回调函数。注意控件值的读取和写入都需要手动实现。

在父组件中,您可以像使用内置表单控件一样使用自定义表单控件:

<app-custom-control [(ngModel)]="value"></app-custom-control>

2. 自定义表单控件实现表单验证

在 Angular 中,表单验证由 Validators 类提供的一些内置验证器完成。同样地,我们也可以自定义表单验证器。

下面是一个自定义表单验证器的示例。该验证器将最小长度设置为4。

import { AbstractControl } from '@angular/forms';

export function minLengthValidator(minLength: number) {
  return (control: AbstractControl) => {
    const value = control.value;
    return value && value.length >= minLength ? null : {
      mininumLength: {
        expectedLength: minLength,
        actualLength: value ? value.length : 0
      }
    };
  };
}

上面的自定义表单验证器接受一个最小长度值作为参数,返回一个验证函数。valid()方法返回null表示验证通过,否则返回一个包含验证失败信息的对象。您可以像下面这样使用自定义表单验证器:

form = this.fb.group({
  customControl: ['', [minLengthValidator(4)]]
});

示例1:自定义单选框控件

如下是一个可以用来展示生肖列表的自定义单选框控件示例:

import { Component, Input, Output, forwardRef, EventEmitter, OnInit, HostBinding } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, AbstractControl } from '@angular/forms';

@Component({
  selector: 'app-custom-checkbox',
  template: `
    <div *ngFor="let item of data">
        <input 
          type="radio"
          [checked]="item.value === value" 
          (blur)="onTouched()"
          [name]="name"
          [value]="item.value"
          (change)="writeValue(item.value)">
          {{item.text}}
    </div>
    `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomRadioComponent),
      multi: true
    }
  ]
})
export class CustomRadioComponent implements ControlValueAccessor {
  @Input() data: any[];
  @Input() name: string;
  @Output() customValueChange = new EventEmitter();
  value: any;

  onTouched: () => void = () => {};
  onChange: (_: any) => void = () => {};

  writeValue(value: any) {
    this.value = value;
    this.customValueChange.emit(this.value);
    this.onTouched();
  }

  registerOnChange(fn: any) {
    this.onChange = fn;
  }
  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean) {
    // 写你自己的代码
  }
}

上述代码中,我们使用了两个模板变量:data 和 name,其中 data 表示一个数组,包含每个单选项的值和显示文本。name 表示单选框的名称。每个单选项都会生成一个单选框元素,并绑定各种属性。

示例2:自定义上传文件组件

下面是一个自定义的上传文件组件,该组件可以允许用户上传多个文件并在上传时展示进度条:

import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-file-upload',
  templateUrl: './file-upload.component.html',
  styleUrls: ['./file-upload.component.scss']
})
export class FileUploadComponent implements OnInit {

  @Input() files: any = [];
  @Input() multiple: boolean = false;
  @Input() label: string = '选择文件';
  @Input() uploadUrl: string = '';
  @Input() uploadFieldName: string = 'file';
  @Input() maxSize?: number = null;
  @Input() accept: string = '*';
  @Output() done: EventEmitter<any> = new EventEmitter<any>();
  @Output() progress: EventEmitter<number> = new EventEmitter<number>();
  @Output() error: EventEmitter<any> = new EventEmitter<any>();
  @Output() removed: EventEmitter<any> = new EventEmitter<any>();
  @Output() added: EventEmitter<any> = new EventEmitter<any>();

  pendingFiles: any[] = [];

  ngOnInit(): void {
  }

  onChange(event) {
    this.pendingFiles = Array.from(event.target.files || []);
    for (let i = 0; i < this.pendingFiles.length; i++) {
      if (this.validate(this.pendingFiles[i])) {
        this.added.emit(this.pendingFiles[i]);
      } else {
        this.pendingFiles.splice(i, 1);
        i--;
      }
    }
  }

  upload() {
    const files = this.pendingFiles.slice(0);
    this.pendingFiles = [];
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      const xhr = new XMLHttpRequest();
      const formData = new FormData()
      formData.append(this.uploadFieldName, file, file.name)
      xhr.onload = () => {
        if (xhr.status === 200) {
          this.done.emit(xhr.responseText);
        } else {
          this.error.emit(xhr.responseText);
        }
        const progress = ((i + 1) / files.length) * 100;
        this.progress.emit(progress);
      }
      xhr.onerror = () => {
        this.error.emit(xhr.responseText);
      }
      xhr.upload.onprogress = (e) => {
        const progress = ((e.loaded / e.total) * 100);
        this.progress.emit(progress);
      }
      xhr.upload.onabort = () => {
        this.removed.emit(file);
      }
      xhr.open('POST', this.uploadUrl, true)
      xhr.send(formData)
    }
  }

  remove(file) {
    const index = this.pendingFiles.indexOf(file);
    if (index !== -1) {
      this.pendingFiles.splice(index, 1);
      this.removed.emit(file);
    }
  }

  validate(file): boolean {
    if (this.maxSize && file.size / 1024 > this.maxSize) {
      this.error.emit('文件超出最大大小');
      return false;
    }
    if (this.accept !== '*' && !new RegExp(this.accept).test(file.type)) {
      this.error.emit('文件类型不允许');
      return false;
    }
    return true;
  }
}

上述代码中,我们使用了一个文件选择器元素来允许用户选择文件。当文件选择器的值发生变化时,我们将选中的文件保存在数组变量pendingFiles中,并校验文件类型和大小。然后我们根据服务器API将文件上传到服务端,并在上传完成后通知使用此组件的父组件以显示上传进度。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅析Angular19 自定义表单控件 - Python技术站

(0)
上一篇 2023年6月25日
下一篇 2023年6月25日

相关文章

  • 浅谈Pycharm的项目文件名是红色的原因及解决方式

    浅谈Pycharm的项目文件名是红色的原因及解决方式 原因 在Pycharm中,项目文件名变红的原因是因为这些文件在VCS(Git、Svn、Mercurial 等版本控制系统)中被标记为 deleted(已删除的)文件或者是未被加入版本控制中的文件。 如果是deleted文件,说明该文件在VCS中被删除了,但是在本地文件系统中还存在,所以文件名会变成红色。 …

    other 2023年6月26日
    00
  • 详解dex优化对Arouter查找路径的影响

    详解DEX优化对Arouter查找路径的影响攻略 什么是DEX优化? DEX优化是指通过优化Android应用程序的最终字节码(Dalvik Executable)和数据布局(Dex Layout)来提升应用程序的启动速度和性能。Android在5.0之后的版本中默认开启DEX优化。 Arouter的工作原理 Arouter是一款Android路由框架,可以…

    other 2023年6月26日
    00
  • C++string函数之strcat_s

    C++string函数之strcat_s 在C++语言中,字符串处理是必不可少的部分。其中,strcat_s()函数是C++中最常用的字符串连接函数之一。 函数简介 strcat_s()函数是Microsoft C++中的字符串连接函数,其定义如下: #include <string.h> errno_t strcat_s(char *strDe…

    其他 2023年3月28日
    00
  • burpsuite的使用(一)

    BurpSuite的使用(一) BurpSuite是一款常用的Web应用安全测试工具,同时也是将安全问题演示给开发者、渗透测试人员等人员的必备工具之一。本文将介绍BurpSuite的基本使用方法:如何使用代理拦截请求,发送请求并对响应进行分析等操作。 下载与安装 BurpSuite官方网站:https://portswigger.net/burp/commu…

    其他 2023年3月28日
    00
  • vue地图可视化arcgis篇

    以下是“Vue地图可视化ArcGIS篇”的完整攻略: Vue地图可视化ArcGIS篇 Vue地图可视化ArcGIS是一种基于Vue和ArcGIS API JavaScript的地图视化解决方案。本攻略将介绍如何使用Vue地图可视化ArcGIS来创建交互式地图。 步骤1:装ArcGIS API for JavaScript 在使用Vue地图可视化ArcGIS之…

    other 2023年5月7日
    00
  • CentOS命令行性能检测工具详解

    下面是“CentOS命令行性能检测工具详解”的完整攻略: CentOS命令行性能检测工具详解 为什么要进行性能检测? 在实际的开发、测试、维护、部署等工作中,我们经常会需要对所运行的系统和应用进行性能检测,以评估其性能瓶颈、寻找优化方案等。而对于类Unix系统(如CentOS)中的命令行应用程序而言,我们可以通过一系列命令行工具进行性能检测,其中包括: to…

    other 2023年6月27日
    00
  • 【vue】vue中遍历数组和对象

    【vue】vue中遍历数组和对象 在vue的开发中,我们经常需要对数组和对象进行遍历,以便在页面中展示数据。本文将介绍vue中遍历数组和对象的两种方式:通过v-for指令遍历和通过js的Object.keys()方法遍历。 遍历数组–v-for指令 在vue中,我们可以通过v-for指令来遍历数组。可以使用v-for指令在template标签中对数组进行遍…

    其他 2023年3月28日
    00
  • JavaScript数据结构之双向链表

    JavaScript数据结构之双向链表是一种常见的数据结构,既可以用于解决实际问题,也可以用于加深对数据结构和算法的理解。下面是这个主题的完整攻略。 概念 双向链表是一种链式存储结构,每个节点包含指向前驱节点和后继节点的指针。相比单向链表,双向链表具有可以双向遍历、插入和删除节点等优势,但同时也存在一些缺点,如结构复杂,占用内存多等。 实现 以下是JavaS…

    other 2023年6月27日
    00
合作推广
合作推广
分享本页
返回顶部