1 需求

  当开发者需要一个特定的表单控件时就需要自己开发一个和默认提供的表单控件用法相似的控件来作为表单控件;自定义的表单控件必须考虑模型和视图之间的数据怎么进行交互

2 官方文档 -> 点击前往

  Angular为开发者提供了ControlValueAccessor接口来辅助开发者构建自定义的表单控件,开发者只需要在自定义表单控件类中实现ControlValueAccessor接口中的方法就可以实现模型和视图之间的数据交互

interface ControlValueAccessor {
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState(isDisabled: boolean)?: void
}

  2.1 writeValue  

writeValue(obj: any): void

    该方法用于将值写入到自定义表单控件中的元素;

    这个参数值(obj)是使用这个自定义表单控件的组件通过模板表单或者响应式表单的数据绑定传过来的;

    在自定义表单控件的类中只需要将这个值(obj)赋值给一个成员变量即可,自定义表单控件的视图就会通过属性绑定显示出来这个值

  2.2 registerOnChange

registerOnChange(fn: any): void

    自定义表单控件的数据发生变化时会触发registerOnChange方法,该方用于如何处理自定义表单控件数据的变化;

    registerOnChange方法接收的参数(fn)其实是一个方法,该方法负责处理变化的数据

    当自定义控件数据变化时就会自动调用fn执行的方法,但是通常的做法是自定义一个方法 propagateChange 让自定义的方法指向fn,这样当数据变化时只需要调用 propagateChange 就可以对变化的数据进行处理

  2.3 registerOnTouched

registerOnTouched(fn: any): void

    表单控件被触摸时会触发registerOnTouched方法,具体细节待更新......2018-1-31 11:18:33

  2.4 setDisabledState

setDisabledState(isDisabled: boolean)?: void

    待更新......2018-1-31 11:19:30

3 编程步骤

  3.1 创建自定义表单控件组件   

<div>
<h4>当前计数为:{{countNumber}}</h4>
<br />
<div>
<button md-icon-button (click)="onIncrease()">
<span>增加</span>
<md-icon>add</md-icon>
</button>
<span style="margin-left: 30px;"></span>
<button md-icon-button (click)="onDecrease()">
<span>减少</span>
<md-icon>remove</md-icon>
</button>
</div>
</div>

HTML

import { Component, OnInit } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms'; @Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.scss']
})
export class CounterComponent implements OnInit { countNumber: number = 0; constructor() { } ngOnInit() {
} onIncrease() {
this.countNumber++;
} onDecrease() {
this.countNumber--;
} }

TS

    3.1.1 功能描述

      点击增加按钮时当前计数会增加1,点击减少按钮时当前计数会剪1

      

    3.1.2 直接在其他组件中使用时会报错

      

      报错信息如下:

        

        错误信息是说我们我们使用的组件<app-counter>还不是一个表单控件

  3.2 如何让<app-counter>组件变成一个表单控件组件

    3.2.1 实现 ControlValueAccessor 接口

      

export class CounterComponent implements OnInit, ControlValueAccessor {

  countNumber: number = 0;

  constructor() { }

  ngOnInit() {
} onIncrease() {
this.countNumber++;
} onDecrease() {
this.countNumber--;
} /**将数据从模型传输到视图 */
writeValue(obj: any): void {
} /**将数据从视图传播到模型 */
registerOnChange(fn: any): void { } registerOnTouched(fn: any): void { } setDisabledState?(isDisabled: boolean): void { } }

    3.2.2 指定依赖信息providers

      

import { Component, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CounterComponent),
multi: true
}
]
})
export class CounterComponent implements OnInit, ControlValueAccessor { countNumber: number = 0; constructor() { } ngOnInit() {
} onIncrease() {
this.countNumber++;
} onDecrease() {
this.countNumber--;
} /**将数据从模型传输到视图 */
writeValue(obj: any): void {
} /**将数据从视图传播到模型 */
registerOnChange(fn: any): void { } registerOnTouched(fn: any): void { } setDisabledState?(isDisabled: boolean): void { } }

    3.2.3 待修复bug

      虽然可以正常运行,但是表单控件中的元素接受不到使用表单控件那个组件中表单模型传过来的数据,表单控件变化的数据也无法回传到使用表单控件那个组件中的表单模型中去;简而言之,就是模型和视图之间无法进行数据交互

  3.3 实现模型和视图的数据交互

    3.3.1 模型到视图

      重构自定义表单控件类中的 writeValue 方法

      技巧01:writeValue 方法中的参数是使用自定义表单控件的那个组件通过表单的数据绑定传进来的

        

    3.3.2 视图到模型

      》自定义一个方法来处理自定义表单控件中的变化数据    

propagateChange = (_: any) => {};

      》重构自定义表单控件类中的 registerOnChange 方法

  /**将数据从视图传播到模型 */
registerOnChange(fn: any): void {
this.propagateChange = fn;
}

      》在数据变化的地方调用那个自定义的方法

        

  3.4 自定义表单控件组件代码汇总

<div>
<h4>当前计数为:{{countNumber}}</h4>
<br />
<div>
<button md-icon-button (click)="onIncrease()">
<span>增加</span>
<md-icon>add</md-icon>
</button>
<span style="margin-left: 30px;"></span>
<button md-icon-button (click)="onDecrease()">
<span>减少</span>
<md-icon>remove</md-icon>
</button>
</div>
</div>

HTML

import { Component, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CounterComponent),
multi: true
}
]
})
export class CounterComponent implements OnInit, ControlValueAccessor { countNumber: number = 0; propagateChange = (_: any) => {}; constructor() { } ngOnInit() {
} onIncrease() {
this.countNumber++;
this.propagateChange(this.countNumber);
} onDecrease() {
this.countNumber--;
this.propagateChange(this.countNumber);
} /**将数据从模型传输到视图 */
writeValue(obj: any): void {
this.countNumber = obj;
} /**将数据从视图传播到模型 */
registerOnChange(fn: any): void {
/**fn其实是一个函数,当视图中的数据改变时就会调用fn指向的这个函数,从而达到将数据传播到模型的目的 */
this.propagateChange = fn; // 将fn的指向赋值给this.propagateChange,在需要将改变的数据传到模型时只需要调用this.propagateChange方法即可
} registerOnTouched(fn: any): void { } setDisabledState?(isDisabled: boolean): void { } }

TS

  3.5 使用自定义表单控件的那个组件的代码汇总

    技巧01:如果自定义表单控件和使用自定义表单控件的组件都在不在同一个模块时需要对自定义表单控件对应组件进行导出和导入操作

    

<div class="panel panel-primary">
<div class="panel-heading">面板模板</div>
<div class="panel-body">
<h3>面板测试内容</h3>
</div>
<div class="panel-footer">2018-1-22 10:22:20</div>
</div> <div class="panel-primary">
<div class="panel-heading">自定义提取表单控件</div>
<div class="panel-body">
<form #myForm=ngForm>
<app-counter name="counter" [(ngModel)]="countNumber">
</app-counter>
</form>
<h6>绿线上是自定义提取的表单控件显示的内容</h6>
<hr style="border: solid green 2px" />
<h6>绿线下是使用自定义表单控件时表单的实时数据</h6>
<h3>表单控件的值为:{{myForm.value | json}}</h3>
</div>
<div class="panel-footer">2018-1-31 10:09:17</div>
</div> <div class="panel-primary">
<div class="panel-heading">提取表单控件</div>
<div class="panel-body">
<form #form="ngForm">
<p>outerCounterValue value: {{outerCounterValue}}</p>
<app-exe-counter name="counter" [(ngModel)]="outerCounterValue"></app-exe-counter>
<br />
<button md-raised-button type="submit">Submit</button>
<br />
<div>
{{form.value | json}}
</div>
</form>
</div>
<div class="panel-footer">2018-1-27 21:51:45</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">ngIf指令测试</div>
<div class="panel-body">
<button md-rasied-button (click)="onChangeNgifValue()">改变ngif变量</button>
<br />
<div *ngIf="ngif; else ngifTrue" >
<h4 style="background-color: red; color: white" >ngif变量的值为true</h4>
</div>
<ng-template #ngifTrue>
<h4 style="background-color: black; color: white">ngif变量的值为false</h4>
</ng-template>
</div>
<div class="panel-footer">2018-1-27 16:58:17</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">RXJS使用</div>
<div class="panel-body">
<h4>测试内容</h4>
</div>
<div class="panel-footer">2018-1-23 21:14:49</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">自定义验证器</div>
<div class="panel-body">
<form (ngSubmit)="onTestLogin()" [formGroup]="loginForm">
<md-input-container>
<input mdInput placeholder="请输入登录名" formControlName="username" />
</md-input-container>
<br />
<md-input-container>
<input mdInput placeholder="请输入密码" formControlName="userpwd" />
</md-input-container>
<br />
<button type="submit" md-raised-button>登陆</button>
</form>
</div>
<div class="panel-footer">2018-1-23 11:06:01</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">响应式表单</div>
<div class="panel-body">
<form [formGroup]="testForm">
<md-input-container>
<input mdInput type="text" placeholder="请输入邮箱" formControlName="email" />
<span mdSuffix>@163.com</span>
</md-input-container>
<br />
<md-input-container>
<input mdInput type="password" placeholder="请输入密码" formControlName="password" />
</md-input-container>
</form>
<hr />
<div>
<h2>表单整体信息如下:</h2>
<h4>表单数据有效性:{{testForm.valid}}</h4>
<h4>表单数据为:{{testForm.value | json}}</h4>
<h4>获取单个或多个FormControl:{{testForm.controls['email'] }}</h4>
<hr />
<h2>email输入框的信息如下:</h2>
<h4>有效性:{{testForm.get('email').valid}}</h4>
<h4>email输入框的错误信息为:{{testForm.get('email').errors | json}}</h4>
<h4>required验证结果:{{testForm.hasError('required', 'email') | json}}</h4>
<h4>minLength验证结果:{{ testForm.hasError('minLength', 'email') | json }}</h4>
<h4>hello:{{ testForm.controls['email'].errors | json }}</h4>
<hr />
<h2>password输入框啊的信息如下:</h2>
<h4>有效性:{{testForm.get('password').valid}}</h4>
<h4>password输入框的错误信息为:{{testForm.get('password').errors | json }}</h4>
<h4>required验证结果:{{testForm.hasError('required', 'password') | json}}</h4>
</div>
<div>
<button nd-rasied-button (click)="onTestClick()">获取数据</button>
<h4>data变量:{{data}}</h4>
</div>
</div>
<div class="panel-footer">2018-1-22 15:58:43</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">利用响应式编程实现表单元素双向绑定</div>
<div class="panel-body">
<md-input-container>
<input mdInput placeholder="请输入姓名(响应式双向绑定):" [formControl]="name"/>
</md-input-container>
<div>
姓名为:{{name.value}}
</div>
</div>
<div class="panel-footer">2018-1-22 11:12:35</div>
</div> --> <div class="panel panel-primary">
<div class="panel-heading">模板表单</div>
<div class="panel-body">
<md-input-container>
<input mdInput placeholder="随便输入点内容" #a="ngModel" [(ngModel)]="desc" name="desc" />
<button type="button" md-icon-button mdSuffix (click)="onTestNgModelClick()">
<md-icon>done</md-icon>
</button>
</md-input-container>
<div>
<h3>名为desc的表单控件的值为:{{ a.value }}</h3>
</div>
</div>
<div class="panel-footer">2018-1-22 10:19:31</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">md-chekbox的使用</div>
<div calss="panel-body">
<div>
<md-checkbox #testCheckbox color="primary" checked="true">测试</md-checkbox>
</div>
<div *ngIf="testCheckbox.checked">
<h2>测试checkbox被选中啦</h2>
</div>
</div>
<div class="panel-footer">2018-1-18 14:02:20</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">md-tooltip的使用</div>
<div class="panel-body">
<span md-tooltip="重庆火锅">鼠标放上去</span>
</div>
<div class="panel-footer">2018-1-18 14:26:58</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">md-select的使用</div>
<div class="panel-body">
<md-select placeholder="请选择目标列表" class="fill-width" style="height: 40px;">
<md-option *ngFor="let taskList of taskLists" [value]="taskList.name">{{taskList.name}}</md-option>
</md-select>
</div>
<div class="panel-footer">2018-1-18 14:26:58</div>
</div> <div class="panel panel-primary">
<div class="panel-heading">ngNonBindable指令的使用</div>
<div class="panel-body">
<h3>描述</h3>
<p>使用了ngNonBindable的标签,会将该标签里面的元素内容全部都看做时纯文本</p>
<h3>例子</h3>
<p>
<span>{{taskLists | json }}</span>
<span ngNonBindable>&larr; 这是{{taskLists | json }}渲染的内容</span>
</p>
</div>
<div class="panel-footer">2018-1-19 09:34:26</div>
</div>

HTML

    

import { Component, OnInit, HostListener, Inject} from '@angular/core';
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Http } from '@angular/http';
import { QuoteService } from '../../service/quote.service'; @Component({
selector: 'app-test01',
templateUrl: './test01.component.html',
styleUrls: ['./test01.component.scss']
})
export class Test01Component implements OnInit { countNumber: number = 9; outerCounterValue: number = 5; ngif = true; loginForm: FormGroup; testForm: FormGroup;
data: any; name: FormControl = new FormControl(); desc: string = 'hello boy'; taskLists = [
{label: 1, name: '进行中'},
{label: 2, name: '已完成'}
]; constructor(
private formBuilder: FormBuilder,
private http: Http,
@Inject('BASE_CONFIG') private baseConfig,
private quoteService: QuoteService
) {} ngOnInit() {
this.testForm = new FormGroup({
email: new FormControl('', [Validators.required, Validators.minLength(4)], []),
password: new FormControl('', [Validators.required], [])
}); this.name.valueChanges
.debounceTime(500)
.subscribe(value => alert(value)); this.loginForm = this.formBuilder.group({
username: ['', [Validators.required, Validators.minLength(4), this.myValidator], []],
userpwd: ['', [Validators.required, Validators.minLength(6)], []]
}); this.quoteService.test()
.subscribe(resp => console.log(resp)); } onChangeNgifValue() {
if (this.ngif == false) {
this.ngif = true;
} else {
this.ngif = false;
}
} @HostListener('keyup.enter')
onTestNgModelClick() {
alert('提交'); } onTestClick() {
// this.data = this.testForm.get('email').value;
// console.log(this.testForm.getError);
console.log(this.testForm.controls['email']);
} onTestLogin() {
console.log(this.loginForm.value);
if (this.loginForm.valid) {
console.log('登陆数据合法');
} else {
console.log('登陆数据不合法');
console.log(this.loginForm.controls['username'].errors);
console.log(this.loginForm.get('userpwd').errors);
}
} myValidator(fc: FormControl): {[key: string]: any} {
const valid = fc.value === 'admin';
return valid ? null : {myValidator: {requiredUsername: 'admin', actualUsername: fc.value}};
} }

TS

  3.6 初始化效果展示

    

  3.7 参考文档

    点击前往

    

Angular19 自定义表单控件的更多相关文章

  1. angular 响应式自定义表单控件—注册头像实例

    1. 组件继承ControlValueAccessor,ControlValueAccessor接口需要实现三个必选方法 writeValue() 用于向元素中写入值,获取表单的元素的元素值 regi ...

  2. Angular:自定义表单控件

    分享一个最近写的支持表单验证的时间选择组件. import {AfterViewInit, Component, forwardRef, Input, OnInit, Renderer} from & ...

  3. AngularJS自定义表单控件

    <!doctype html> <html ng-app="myApp"> <head> <script src="G:\\So ...

  4. MVC树控件,mvc中应用treeview,实现复选框树的多层级表单控件

    类似于多层级的角色与权限控制功能,用MVC实现MVC树控件,mvc中应用treeview,实现复选框树的多层级表单控件.最近我们的项目中需要用到树型菜单,以前使用WebForm时,树型菜单有微软提供的 ...

  5. Vue.js学习笔记——表单控件实践

    最近项目中使用了vue替代繁琐的jquery处理dom的数据更新,个人非常喜欢,所以就上官网小小地实践了一把. 以下为表单控件的实践,代码敬上,直接新建html文件,粘贴复制即可看到效果~ <! ...

  6. C#中缓存的使用 ajax请求基于restFul的WebApi(post、get、delete、put) 让 .NET 更方便的导入导出 Excel .net core api +swagger(一个简单的入门demo 使用codefirst+mysql) C# 位运算详解 c# 交错数组 c# 数组协变 C# 添加Excel表单控件(Form Controls) C#串口通信程序

    C#中缓存的使用   缓存的概念及优缺点在这里就不多做介绍,主要介绍一下使用的方法. 1.在ASP.NET中页面缓存的使用方法简单,只需要在aspx页的顶部加上一句声明即可:  <%@ Outp ...

  7. 认识HTML中表格、列表标签以及表单控件

    前端之HTML,CSS(二) HTML标签 列表标签 无序列表:闭标签,由<ul><li></li>...</ul>组合而成,效果成纵向列表.格式:&l ...

  8. 仿苹果电脑任务栏菜单&&拼图小游戏&&模拟表单控件

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  9. HTML入门(列表、表单、常用表单控件、浮动框架、iframe、 摘要与细节、度量标签)

    一.列表 1.作用:默认显示方式为从上到下的显示数据 2.列表的组成 列表类型和列表项 3.列表的分类:有序列表   无序列表   自定义列表 无序列表语法为ul>li, 语法:ul代表列表,l ...

随机推荐

  1. 架构师之路->架构师思维的培养

    公司的CMS(综合赋码管理系统)是WINFORM的CS架构.这套系统的架构师换了3届,到现在已经几年没有架构师了.本来入职时,岗位目标就是这个“自动化架构师”. 后来和领导达成共识先争取成为储备架构师 ...

  2. sqlserver资源

    1.数据库“高可用性”和“灾难恢复”技术 参考: niyi0318的专栏

  3. PyQt4 模拟记事本基本功能(保存,打开文件)

    完成功能: 1. 默认[保存]按钮enable 2. 修改文本的内容后,[enable] 3. 解决字符乱码问题:utf-8 4. 提示:如果修改了文件没有保存的时候,又尝试打开新的文件,给出相关的提 ...

  4. C# DataSet数据导入Excel 修正版- .net FrameWork 4.0以上

    引入  Microsoft.Office.Interop.Excel.dll 格式:标题加了下划线,单元格内容居中 1 using System; using System.Data; using S ...

  5. tornado SSL 证书获取与服务器配置

    转载注明出处: http://www.cnblogs.com/ityoung/p/8296088.html 自动化测试/持续集成/测试开发 QQ交流群: 70160503 服务端生成证书 进入 ope ...

  6. 日期插件-flatpickr

    github的仓库地址:https://github.com/chmln/flatpickr 手册地址:http://www.htmleaf.com/Demo/201608213895.html    ...

  7. Kubernetes 架构(上)- 每天5分钟玩转 Docker 容器技术(120)

    Kubernetes Cluster 由 Master 和 Node 组成,节点上运行着若干 Kubernetes 服务. Master 节点 Master 是 Kubernetes Cluster ...

  8. 用Windbg来分析.Net程序的dump

    介绍 1. 什么是Windbg WinDbg是微软发布的一款相当优秀的源码级(source-level)调试工具,可以用于Kernel模式调试和用户模式调试,还可以调试Dump文件. WinDbg是微 ...

  9. Java_web学习(一) jdk配置

    1.下载好jdk1.8.0版本或以上版本 2.配置JAVA_HOME,CLASSPATH,PATH 其中JAVA_HOME必须的 2.1   JAVA_HOME=E:\java\jdk1.8.0_77 ...

  10. Friday for Oldboy

    计算机的硬件介绍 1.  CPU的工作流程:取指令->解码->执行 .  程序状态字寄存器(Program Status Word,PSW)中有一个二进制位控制这两种模式. 内核态:当cp ...