浅析Angular19 自定义表单控件


Posted in Javascript onJanuary 31, 2018

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

3.1.1 功能描述

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

浅析Angular19 自定义表单控件 

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

浅析Angular19 自定义表单控件 

报错信息如下:

浅析Angular19 自定义表单控件 

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

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

3.2.1 实现 ControlValueAccessor 接口

浅析Angular19 自定义表单控件 

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

浅析Angular19 自定义表单控件 

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 方法中的参数是使用自定义表单控件的那个组件通过表单的数据绑定传进来的

浅析Angular19 自定义表单控件 

3.3.2 视图到模型

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

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

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

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

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

浅析Angular19 自定义表单控件 

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

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

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

浅析Angular19 自定义表单控件 

<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>← 这是{{taskLists | json }}渲染的内容</span>
 </p>
 </div>
 <div class="panel-footer">2018-1-19 09:34:26</div>
</div>

HTML

浅析Angular19 自定义表单控件 

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

3.6 初始化效果展示

浅析Angular19 自定义表单控件 

Javascript 相关文章推荐
学习YUI.Ext 第六天--关于树TreePanel(Part 2异步获取节点)
Mar 10 Javascript
jQuery中事件对象e的事件冒泡用法示例介绍
Apr 25 Javascript
调整小数的格式保留小数点后两位
May 14 Javascript
javascript将浮点数转换成整数的三个方法
Jun 23 Javascript
JsRender实用入门教程
Oct 31 Javascript
js实现简单的左右两边固定广告效果实例
Apr 10 Javascript
JS实现简单的图书馆享元模式实例
Jun 30 Javascript
详解jQuery移动页面开发中的ui-grid网格布局使用
Dec 03 Javascript
javascript每日必学之基础入门
Feb 16 Javascript
canvas的神奇用法
Feb 03 Javascript
浅谈vue中组件绑定事件时是否加.native
Nov 09 Javascript
vue+ts下对axios的封装实现
Feb 18 Javascript
JavaScript实现计算多边形质心的方法示例
Jan 31 #Javascript
微信小程序switch开关选择器使用详解
Jan 31 #Javascript
详解Angular调试技巧之报错404(not found)
Jan 31 #Javascript
微信小程序slider组件使用详解
Jan 31 #Javascript
vue项目实现记住密码到cookie功能示例(附源码)
Jan 31 #Javascript
AngularJS 将再发布一个重要版本 然后进入长期支持阶段
Jan 31 #Javascript
微信小程序progress组件使用详解
Jan 31 #Javascript
You might like
PHP框架实现WebSocket在线聊天通讯系统
2019/11/21 PHP
Javascript实例教程(19) 使用HoTMetal(7)
2006/12/23 Javascript
JavaScript Undefined,Null类型和NaN值区别
2008/10/22 Javascript
javascript 多浏览器 事件大全
2010/03/23 Javascript
javascript中字符串替换函数replace()方法与c# 、vb 替换有一点不同
2010/06/25 Javascript
属于你的jQuery提示框(Tip)插件
2016/01/20 Javascript
JavaScript实现DOM对象选择器
2016/09/24 Javascript
js实现字符全排列算法的简单方法
2017/05/01 Javascript
Angular.JS通过指令操作DOM的方法
2017/05/10 Javascript
深入浅析Vue中mixin和extend的区别和使用场景
2019/08/01 Javascript
javascript刷新父页面方法汇总详解
2019/10/10 Javascript
js实现一款简单踩白块小游戏(曾经很火)
2019/12/02 Javascript
简单了解JS打开url的方法
2020/02/21 Javascript
javascript利用canvas实现鼠标拖拽功能
2020/07/23 Javascript
[53:21]2014 DOTA2国际邀请赛中国区预选赛5.21 DT VS LGD-CDEC
2014/05/22 DOTA
Python处理字符串之isspace()方法的使用
2015/05/19 Python
Python聚类算法之基本K均值实例详解
2015/11/20 Python
python3新特性函数注释Function Annotations用法分析
2016/07/28 Python
Python 'takes exactly 1 argument (2 given)' Python error
2016/12/13 Python
机器学习python实战之手写数字识别
2017/11/01 Python
python实现银联支付和支付宝支付接入
2019/05/07 Python
使用python制作一个为hex文件增加版本号的脚本实例
2019/06/12 Python
Python函数式编程指南:对生成器全面讲解
2019/11/19 Python
Python如何基于rsa模块实现非对称加密与解密
2020/01/03 Python
聪明的粉丝购买门票的地方:TickPick
2018/03/09 全球购物
Ajxa常见问题都有哪些
2014/03/26 面试题
学前教育毕业生自荐信
2013/10/29 职场文书
校园创业策划书
2014/01/14 职场文书
餐饮管理自我介绍信
2014/01/15 职场文书
动员大会主持词
2014/03/20 职场文书
护士先进个人总结
2015/02/13 职场文书
班主任工作总结范文
2015/08/13 职场文书
护理自荐信
2019/05/14 职场文书
工作汇报材料难写?方法都在这里了!
2019/07/01 职场文书
小学生作文写作技巧100例,非常实用!
2019/07/08 职场文书
python实现求纯色彩图像的边框
2021/04/08 Python