angular4自定义表单控件[(ngModel)]的实现


Posted in Javascript onNovember 23, 2018

[(ngModel)]拆分

[(ngModel)][]输入()输出组合起来,进行双向数据绑定。拆分开来

  • 输入属性
  • [ngModel](ngModelChange)输出监听元素值的变化,并同步view value与model value。
<input type="text" id="modelInner" [ngModel]="model" (ngModelChange)="getModelChange($event)">
model: string;
  constructor() {
    this.model = 'model init';
  }

  getModelChange(event: string) {
    this.model = event; // view value 与 model value 同步
  }

自定义组件上使用 [(ngModel)]

我们不能把[(ngModel)]用到非表单类的原生元素或第三方自定义组件上,除非写一个合适的值访问器,这种技巧超出了本章的范围。

Angular文档中描述到这里,就中止了。刚好我要定制一个模拟radio的组件,只能如文档所说,依葫芦画瓢实现 ControlValueAccessor

ControlValueAccessor接口

ControlValueAccessor acts as a bridge between the Angular forms API and a native element in the DOM.
Implement this interface if you want to create a custom form control directive that integrates with Angular forms.

简而言之,实现了这个接口的组件,就可以使用 Angular forms API,比如[(ngModel)]

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

实现ControlValueAccessor步骤

模仿primeng中的自定义radio组件,写了一个简单的自定义radio组件。

  • 创建一个RADIO_VALUE_ACCESSOR常量用来在组件中注册NG_VALUE_ACCESSOR
  • 实现ControlValueAccessor中的3+1个方法

完整demo代码如下:

import { NgModule, Component, Input, Output, ElementRef, OnInit, EventEmitter, forwardRef, ViewChild, ChangeDetectorRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

const RADIO_VALUE_ACCESSOR: any = {
 provide: NG_VALUE_ACCESSOR,
 useExisting: forwardRef(() => PRadioComponent),
 multi: true
};

@Component({
 selector: 'app-p-radio',
 template: `
    <div class="p-radio">
      <label class="radio-label" (click)="select()" *ngIf="label">
        <div class="name" [class.checked-name]="rb.checked">{{label}}</div>
      </label>
      <div class="helper-hidden-accessible">
        <input #rb type="radio" [attr.name]="name" [attr.value]="value" [checked]="checked">
      </div>
      <div class="radio-md" (click)="handleClick()">
        <div class="radio-icon " [class.radio-checked]="rb.checked">
           <div class="radio-inner"></div>
        </div>
      </div>
    </div>
  `,
 styleUrls: ['./p-radio.component.scss'],
 providers: [RADIO_VALUE_ACCESSOR]
})
export class PRadioComponent implements ControlValueAccessor {

 @Input() name: string;
 @Input() label: string;
 @Input() value: string;
 checked: boolean;

 @ViewChild('rb') inputViewChild: ElementRef;
 @Output() pRadioChange: EventEmitter<any> = new EventEmitter();
 onModelChange: Function = () => { };

 constructor(
  private cd: ChangeDetectorRef
 ) { }

 // model view -> view value
 writeValue(value: any): void {
  if (value) {
   this.checked = (value === this.value);
   if (this.inputViewChild.nativeElement) {
    this.inputViewChild.nativeElement.checked = this.checked;
   }
   this.cd.markForCheck();
  }
 }

 // view value ->model value
 registerOnChange(fn: Function): void {
  this.onModelChange = fn;
 }

 registerOnTouched(fn: Function): void { }

 handleClick() {
  this.select();
 }

 select() {
  this.inputViewChild.nativeElement.checked = !this.inputViewChild.nativeElement.checked;
  this.checked = !this.checked;
  if (this.checked) {
   this.onModelChange(this.value); // 同步view value 和 model value
  } else {
   this.onModelChange(null);
  }
  this.pRadioChange.emit(null);
 }

}

@NgModule({
 imports: [CommonModule],
 exports: [PRadioComponent],
 declarations: [PRadioComponent]
})

export class RadioButtonModule { }

方法何时被调用?

writeValue(obj: any): void

API中提到 (model -> view) 时,writeValue() 会被调用。
model value 和 view value分别指什么?
举个调用PRadioComponent的例子:

<app-p-radio [value]="'1'" [label]="'text1'" [(ngModel)]="checkedValue"></app-p-radio>

这里checkedValue属性就是model value,view value 为PRadioComponent内部的某个属性(PRadioComponent中定义为this.value)。

当model view(checkedValue)发生改变时,PRadioComponent中的writeValue(obj: any)就会被调用,参数为当前model value(checkedValue)的值,在函数中将参数值赋给内部的view value,从而实现(model -> view)。接受到model value的值后,改变PRadioComponent的UI显示。

registerOnChange(fn: any): void

这个方法的作用是同步 view value 和 model value (view -> model),

registerOnChange(fn: Function): void {
  this.onModelChange = fn;
 }

调用this.onModelChange()时候,将view value当作参数传入此方法中,即完成了同步,此例子中this.onModelChange(this.value);

上面两种方法是相对的:

  • writeValue(obj: any): model value发生改变 ,完成后UI发生改变(model value-> view value)
  • registerOnChange(fn: any): 触发事件(比如click),view value和UI发生改变,完成调用后model value与view value同步(view value-> model value)

registerOnTouched(fn: any): void

setDisabledState(isDisabled: boolean)?: void

目的只为在控件中简单的使用[(ngModel)],所以这两个方法没有用到。registerOnTouched(fn: any)必须实现,所以定义了一个空函数。

实际效果

初始值为'a',点击改变view value,在Angury调试工具中看到值改为'b'。然后在调试工具中将checkedValue改为'a',视图发生了改变。可见,完成了数据的双向绑定。

angular4自定义表单控件[(ngModel)]的实现

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
初学js 新节点的创建 删除 的步骤
Jul 04 Javascript
详谈 Jquery Ajax异步处理Json数据.
Sep 09 Javascript
fancybox modal的完美解决(右上的X)
Oct 30 Javascript
jquery实现倒计时效果
Dec 14 Javascript
javascript实现保留两位小数的多种方法
Dec 18 Javascript
JS随机洗牌算法之数组随机排序
Mar 23 Javascript
JS不用正则验证输入的字符串是否为空(包含空格)的实现代码
Jun 14 Javascript
AngularJS动态加载模块和依赖的方法分析
Nov 08 Javascript
原生js和css实现图片轮播效果
Feb 07 Javascript
Vue从TodoList中学父子组件通信
Feb 05 Javascript
javascript实现计算器功能
Mar 30 Javascript
vue vant中picker组件的使用
Nov 03 Javascript
详解Angular中实现自定义组件的双向绑定的两种方法
Nov 23 #Javascript
Vue.js组件间通信方式总结【推荐】
Nov 23 #Javascript
vue-cli 2.*中导入公共less文件的方法步骤
Nov 22 #Javascript
vue全局使用axios的方法实例详解
Nov 22 #Javascript
vue中的ref和$refs的使用
Nov 22 #Javascript
浅析vue 函数配置项watch及函数 $watch 源码分享
Nov 22 #Javascript
原生JS实现手动轮播图效果实例代码
Nov 22 #Javascript
You might like
PHP面向对象程序设计之类常量用法实例
2014/08/20 PHP
9个经典的PHP代码片段分享
2014/12/18 PHP
PHP针对字符串开头和结尾的判断方法
2016/07/11 PHP
YII框架http缓存操作示例
2019/04/29 PHP
jQuery数组处理方法汇总
2011/06/20 Javascript
IE6-8中Date不支持toISOString的修复方法
2014/05/04 Javascript
学习javascript面向对象 理解javascript原型和原型链
2016/01/04 Javascript
使用Promise解决多层异步调用的简单学习心得
2016/05/17 Javascript
JavaScript进阶练习及简单实例分析
2016/06/03 Javascript
浅析如何利用JavaScript进行语音识别
2016/10/27 Javascript
使用JS 插件qrcode.js生成二维码功能
2017/02/20 Javascript
Web开发中客户端的跳转与服务器端的跳转的区别
2017/03/05 Javascript
JS中图片压缩的方法小结
2017/11/14 Javascript
vue-cli系列之vue-cli-service整体架构浅析
2019/01/14 Javascript
小程序实现按下录音松开识别语音
2019/11/22 Javascript
微信小程序订阅消息(java后端实现)开发
2020/06/01 Javascript
Antd-vue Table组件添加Click事件,实现点击某行数据教程
2020/11/17 Javascript
Python中atexit模块的基本使用示例
2015/07/08 Python
python获取命令行输入参数列表的实例代码
2018/06/23 Python
使用Template格式化Python字符串的方法
2019/01/22 Python
Python OpenCV之图片缩放的实现(cv2.resize)
2019/06/28 Python
django搭建项目配置环境和创建表过程详解
2019/07/22 Python
pyinstaller打包opencv和numpy程序运行错误解决
2019/08/16 Python
使用Python脚本zabbix自定义key监控oracle连接状态
2019/08/28 Python
python定时任务 sched模块用法实例
2019/11/04 Python
Python高级编程之继承问题详解(super与mro)
2019/11/19 Python
Python求解正态分布置信区间教程
2019/11/20 Python
Matplotlib使用字符串代替变量绘制散点图的方法
2020/02/17 Python
Python小白垃圾回收机制入门
2020/06/09 Python
python Matplotlib模块的使用
2020/09/16 Python
科颜氏加拿大官方网站: Kiehl’s加拿大
2016/08/16 全球购物
一加手机美国官方网站:OnePlus美国
2019/09/19 全球购物
医药营销个人求职信范文
2014/02/07 职场文书
先进工作者获奖感言
2014/02/08 职场文书
好人好事演讲稿
2014/09/01 职场文书
计划生育诚信协议书
2014/11/02 职场文书