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

查看完整代码
Angular写一个Form组件-TagInput的更多相关文章
- 写一个vue组件
写一个vue组件 我下面写的是以.vue结尾的单文件组件的写法,是基于webpack构建的项目.如果还不知道怎么用webpack构建一个vue的工程的,可以移步到vue-cli. 一个完整的vue组件 ...
- js单行写一个评级组件
单行写一个评级组件:"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate); -----------------------------------分隔符- ...
- 表单配置项写法,表单写成JSON数组套对象,一行是一个数组单位,一列是一个对象单位,然后再写一个公共组件读取这个配置,循环加载slot,外层载入slot的自定义部分,比如input select等,这种写法就是把组件嵌套改为配置方式
表单配置项写法,表单写成JSON数组套对象,一行是一个数组单位,一列是一个对象单位,然后再写一个公共组件读取这个配置,循环加载slot,外层载入slot的自定义部分,比如input select等,这 ...
- Vue3语法快速入门以及写一个倒计时组件
Vue3写一个倒计时组件 vue3 beta版本发布已有一段时间了,文档也大概看了一下,不过对于学一门技术,最好的方法还是实战,于是找了一个比较简单的组件用vue3来实现,参考的是vant的count ...
- 手动实现一个form组件
最近研究了一下element-ui,想着手动实现一下里面的form组件,贴个组件里面的代码 <el-form :model="ruleForm" status-icon :r ...
- [翻译]怎么写一个React组件库(一)
本文同步发布于知乎专栏 https://zhuanlan.zhihu.com/p/27401329,喜欢本文的就去知乎点个赞支持下吧- 引言 该系列文章将通过创建一个组件库来引导你学习如何构建自己的组 ...
- HTML+CSS+JS(+Vue)写一个通讯录组件
求各位大大的Star(*/ω\*). 没有录屏,所以上传的是图片.后面已补充录屏效果. 效果:(主要是参考小米Note3的通讯录的效果做的) 主要功能: 1. 滚动后,通讯录的模块标题会固定在顶部(图 ...
- 手写一个admin 组件------STARK
开一个新的项目,,建立一个stark 包, 在里面创建一个service包,在service 包里创建一个stark.py 文件, 配置好环境, makemigreations, migreate. ...
- 在React中写一个Animation组件,为组件进入和离开加上动画/过度
问题 在单页面应用中,我们经常需要给路由的切换或者元素的挂载和卸载加上过渡效果,为这么一个小功能引入第三方框架,实在有点小纠结.不如自己封装. 思路 原理 以进入时opacity: 0 --> ...
随机推荐
- springboot源码解析-管中窥豹系列之项目类型(二)
一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...
- WDCP v3 安装
---已更新至3.0.3---经过近期的努力,wdCP_v3正式版终于可以和大家见面了v3功能预览1 底层完全重新架构,更安全稳定,省资源更高效2 安装更简单,快速与方便3 功能更强大和易扩展,且完美 ...
- [leetcode] 周赛 223
比赛题目:https://leetcode-cn.com/contest/weekly-contest-223/. 解码异或后的数组 题目:1720. 解码异或后的数组. 还记得数列求和的「累加法」? ...
- git的简单使用方式(基本操作部分)
git的简单使用方式(基本操作部分) 1.简单介绍GIT的工作流程 git一般的工作流程: 克隆git的资源作为工作目录(一般会使用命令git clone进行克隆); 在克隆的资源上对文件进行增加或者 ...
- 010_MySQL
目录 初识MySQL 为什么学习数据库 什么是数据库 数据库分类 MySQL简介 Windows安装MySQL 安装建议 软件下载 安装步骤 安装SQLyog 下载安装 连接数据库 简单操作 命令行连 ...
- 实验一-最小生成树Kruskal算法
实验名称 最小生成树算法-Kruskal算法 实验目的 1.掌握并查集的合并优化和查询优化: 2.掌握Kruskal算法. 3.能够针对实际问题,能够正确选择贪心策略. 4.能够针对选择的贪心策略,证 ...
- Java开发手册之工程结构
1.在线上生产环境,JVM 的 Xms 和 Xmx 设置一样大小的内存容量,避免在 GC 后调整堆大小带来的压力. 2.给 JVM 环境参数设置-XX:+HeapDumpOnOutOfMemoryEr ...
- 【Linux】1、命令行及命令参数
命令行及命令参数 文章目录 命令行及命令参数 1.命令行提示符 2.命令和命令参数 简单的命令 date ls 命令参数 短参数(一个字母) 长参数(多个字母) 参数的值 其它参数 3.小结 4.参考 ...
- BAPI_PO_CHANGE
这两天用BAPI更改采购订单,遇到了一些问题,最后调试解决了.记录如下吧.要修改的是采购订单的物料号和批次,在网上看到其它人写过关于 BAPI_PO_CHANGE的用法,但是具体问题还要具体分析啊. ...
- 关于SQL Server 镜像数据库快照的创建及使用
从SQL Server 2005 SP 起,SQL 开始支持数据库镜像.它的设计目的是试图为SQL Server 提供一个具有实时性数据同步的灾难恢复技术,即能够提供数据冗余备份,切换起来比较方便.每 ...