1 需求

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

2 官方文档 -> 点击前往

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

interface ControlValueAccessor { writeValue(obj: any): voidregisterOnChange(fn: any): voidregisterOnTouched(fn: any): voidsetDisabledState(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 {}}

View Code

    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 {}}

View Code

    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 参考文档

    点击前往

    

转载于:https://www.cnblogs.com/NeverCtrl-C/p/8391533.html

Angular19 自定义表单控件相关推荐

  1. Angular 4.x 自定义表单控件

    当我们打算自定义表单控件前,我们应该先考虑一下以下问题: 是否已经有相同语义的 native (本机) 元素?如:<input type="number"> 如果有,我 ...

  2. Angular学习笔记(五) - 自定义表单控件

    本文简单介绍封装使用ngModel实现自定义表单控件的过程. NgModel 相关 NgModel NgModel用于从作用域创建一个FormControl实例,并将它绑定到一个表单控件元素. ngM ...

  3. Angular: [ControlValueAccessor] 自定义表单控件

    Angular: [ControlValueAccessor] 自定义表单控件 我们在实际开发中,通常会遇到各种各样的定制化功能,会遇到有些组件会与 Angular 的表单进行交互,这时候我们一般会从 ...

  4. 细说 Angular 的自定义表单控件

    我们在构建企业级应用时,通常会遇到各种各样的定制化功能,因为每个企业都有自己独特的流程.思维方式和行为习惯.有很多时候,软件企业是不太理解这种情况,习惯性的会给出一个诊断,『你这么做不对,按逻辑应该这 ...

  5. 细说 Angular 的自定义表单控件 (赞,实用、日期组件)

    原文出处:https://m.imooc.com/article/19369  (应该是<Angular从零到一>作者) 我们在构建企业级应用时,通常会遇到各种各样的定制化功能,因为每个企 ...

  6. vb.net form 最大化按钮 代码_【React】利用antd的form自定义表单控件

    由于业务的需求,需要对Form表单进行自定义控件操作 业务需求如下: 首先点击选择按钮---在弹窗中选择产品--将选择好的产品展示在页面上,关于自定义组件的封装网上大牛的方法大多是封装好新的组件,从而 ...

  7. Antd Form 自定义表单控件

    首先我们直接在自定义组件中打印看一下props能得到什么: 可以看到Form.Item向我们的自定义组件内部传递了一个value和一个onChange事件 那么我们可以在自己定义的组件内部维护好一个s ...

  8. antd自定义form表单控件

    用 getFieldDecorator 方法包裹的表单控件会自动添加 value (或由 valuePropName 指定的属性名) 和 onChange (或由 trigger 指定的属性名)属性, ...

  9. 『ExtJS』表单(一)常用表单控件及内置验证

    几点说明 关于ExtJS的表单,我打算分为三个部分来写 常用表单控件及内置验证 -- 这里主要是JS代码 表单行为与Asp.NET页面的消息回复 -- 这里既有JS代码,与有C#代码,我主要是使用As ...

最新文章

  1. 在CSS中定义a:link、a:visited、a:hover、a:active顺序
  2. MFC程序在控制台输出的实现
  3. java相关是什么,什么是java
  4. 什么是预热 压测_全链路压测探索实践之路
  5. 社招2017-阿里、网易、滴滴共十次前端面试碰到的问题
  6. 性能测试工具JMeter的安装及环境配置--Windows和Linux
  7. SharePoint 2013 激活标题字段外的Menu菜单
  8. 中国智能手机行业的江湖事
  9. gmssl java api_关于GmSSL Java API编译
  10. php 连接芒果数据库,芒果数据库mongoDB
  11. 2019腾讯广告算法大赛之清洗广告数据集和用户数据集
  12. 高中英语语法(003)-句子的要素及种类
  13. ACM算法模板小汇总
  14. PL330 DMAC笔记(3) - 外设请求接口,事件和中断,Abort
  15. 梅特勒-托利多 TCS-35 电子台秤
  16. 三菱FX5U控制10轴伺服的设备成套电气图纸
  17. npm run build:具体
  18. UE5 植被系统详解
  19. 2022 年的 React 生态
  20. Pj Immediate Decodability

热门文章

  1. 解决启动httpd报: apr_sockaddr_info_get() failed for错误
  2. 《LeetCode力扣练习》第461题 汉明距离 Java
  3. python select模块安装_python+selenium select下拉选择框定位处理方法
  4. php mysql可以跨站_PHP防跨站之open_basedir目录设置
  5. asp怎么循环增加字段和字段对应的值_ASP.NET Core教程「三」实体字段属性
  6. bde oracle 商友的流程_bde oracle 商友的流程_BDE动态连接Oracle数据库
  7. python可以用于机电控制么_都知道砂石骨料可以用于建筑,但是砂石的分类你都清楚么?...
  8. NG-ZORRO 表格多选框改为单选框 (angular框架)
  9. 托管代码与非托管代码
  10. c语言复杂性,C语言复杂函数