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 自动安装exe程序
Nov 30 Javascript
javascript 面向对象 function类
May 13 Javascript
扩展jquery实现客户端表格的分页、排序功能代码
Mar 16 Javascript
自己写了一个展开和收起的多更能型的js效果
Mar 05 Javascript
jquery和ajax的关系详细介绍
Nov 29 Javascript
利用JavaScript检测CPU使用率自己写的
Mar 22 Javascript
javascript获取隐藏元素(display:none)的高度和宽度的方法
Jun 06 Javascript
JavaScript实现简单的tab选项卡切换
Jan 05 Javascript
用jQuery实现优酷首页轮播图
Jan 09 Javascript
jQuery实现弹窗下底部页面禁止滑动效果
Dec 19 jQuery
jQuery发请求传输中文参数乱码问题的解决方案
May 22 jQuery
Vue鼠标滚轮滚动切换路由效果的实现方法
Aug 04 Vue.js
详解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实现下载文件的两种方法
2013/07/05 PHP
PHP生成随机密码类分享
2014/06/25 PHP
PHP读取Excel类文件
2017/05/15 PHP
PHP策略模式定义与用法示例
2017/07/27 PHP
PHP排序算法之归并排序(Merging Sort)实例详解
2018/04/21 PHP
浅谈Laravel核心解读之Console内核
2018/12/02 PHP
javascript 显示当前系统时间代码
2009/12/28 Javascript
Javascript面象对象成员、共享成员变量实验
2010/11/19 Javascript
JS动态添加与删除select中的Option对象(示例代码)
2013/12/20 Javascript
javascript写的异步加载js文件函数(支持数组传参)
2014/06/07 Javascript
jquery中页面Ajax方法$.load的功能使用介绍
2014/10/20 Javascript
JavaScript合并两个数组并去除重复项的方法
2015/06/13 Javascript
jQuery实现div随意拖动的实例代码(通用代码)
2016/01/28 Javascript
JavaScript中的闭包
2016/02/24 Javascript
jQuery插件FusionCharts实现的MSBar2D图效果示例【附demo源码】
2017/03/24 jQuery
使用bootstraptable插件实现表格记录的查询、分页、排序操作
2017/08/06 Javascript
微信小程序wepy框架学习和使用心得详解
2019/05/24 Javascript
Vue使用vue-recoure + http-proxy-middleware + vuex配合promise实现基本的跨域请求封装
2019/10/21 Javascript
js实现查询商品案例
2020/07/22 Javascript
[56:12]LGD vs Optic Supermajor小组赛D组胜者组决赛 BO3 第一场 6.3
2018/06/04 DOTA
Python实现的简单hangman游戏实例
2015/06/28 Python
Python端口扫描简单程序
2016/11/10 Python
Python基于回溯法子集树模板实现8皇后问题
2017/09/01 Python
tensorflow 用矩阵运算替换for循环 用tf.tile而不写for的方法
2018/07/27 Python
pyqt5的QComboBox 使用模板的具体方法
2018/09/06 Python
导入tensorflow时报错:cannot import name 'abs'的解决
2019/10/10 Python
python通过移动端访问查看电脑界面
2020/01/06 Python
python图形开发GUI库pyqt5的详细使用方法及各控件的属性与方法
2020/02/14 Python
Python3标准库之dbm UNIX键-值数据库问题
2020/03/24 Python
世界顶级户外运动品牌折扣网站:LeftLane Sports
2019/06/12 全球购物
优秀工会工作者事迹材料
2014/06/02 职场文书
求职意向书
2014/07/29 职场文书
开除员工通知
2015/04/22 职场文书
大学生心理健康活动总结
2015/05/08 职场文书
感谢师恩主题班会
2015/08/17 职场文书
广播稿:校园广播稿范文
2019/04/17 职场文书