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教程:window.location使用方法的区别介绍
Oct 04 Javascript
java中String类型变量的赋值问题介绍
Mar 23 Javascript
JavaScript的Ext JS框架中的GridPanel组件使用指南
May 21 Javascript
BootStrap创建响应式导航条实例代码
May 31 Javascript
jQuery实现点击查看大图并以弹框的形式居中
Aug 08 Javascript
AngularJS封装指令方法详解
Dec 12 Javascript
JS出现失效的情况总结
Jan 20 Javascript
使用JavaScript根据图片获取条形码的方法
Jul 04 Javascript
深入koa-bodyparser原理解析
Jan 16 Javascript
在JavaScript中如何访问暂未存在的嵌套对象
Jun 18 Javascript
vue简单练习 桌面时钟的实现代码实例
Sep 19 Javascript
vue实现节点增删改功能
Sep 26 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出现Cannot modify header information问题的解决方法大全
2008/04/09 PHP
php 字符串替换的方法
2012/01/10 PHP
php生成html文件方法总结
2014/12/01 PHP
使用jquery实现以post打开新窗口
2014/03/19 Javascript
Js实现手机发送验证码时按钮延迟操作
2014/06/20 Javascript
js完美实现@提到好友特效(兼容各大浏览器)
2015/03/16 Javascript
js获取url传值的方法
2015/12/18 Javascript
jQuery树形插件jquery.simpleTree.js用法分析
2016/09/05 Javascript
jQuery阻止移动端遮罩层后页面滚动
2017/03/15 Javascript
IScroll5实现下拉刷新上拉加载的功能实例
2017/08/11 Javascript
vue脚手架搭建过程图解
2018/06/06 Javascript
vue2.0 + ele的循环表单及验证字段方法
2018/09/18 Javascript
js删除对象/数组中null、undefined、空对象及空数组方法示例
2018/11/14 Javascript
详解微信小程序获取当前时间及日期的方法
2019/04/28 Javascript
Moment.js实现多个同时倒计时
2019/08/26 Javascript
vuex state中的数组变化监听实例
2019/11/06 Javascript
纯JS开发baguetteBox.js响应式画廊插件
2020/06/28 Javascript
[06:49]2018DOTA2国际邀请赛寻真——VirtusPro傲视群雄
2018/08/12 DOTA
python根据距离和时长计算配速示例
2014/02/16 Python
python读取浮点数和读取文本文件示例
2014/05/06 Python
Python中的装饰器用法详解
2015/01/14 Python
Python自定义装饰器原理与用法实例分析
2018/07/16 Python
python中redis查看剩余过期时间及用正则通配符批量删除key的方法
2018/07/30 Python
Python中拆分字符串的操作方法
2019/07/23 Python
Python 中pandas索引切片读取数据缺失数据处理问题
2019/10/09 Python
python中子类与父类的关系基础知识点
2021/02/02 Python
基于PyInstaller各参数的含义说明
2021/03/04 Python
世界领先的电子书网站:eBooks.com(在线购买小说、非小说和教科书)
2019/03/30 全球购物
锐步英国官网:Reebok英国
2019/11/29 全球购物
在对linux系统分区进行格式化时需要对磁盘簇(或i节点密度)的大小进行选择,请说明选择的原则
2012/01/13 面试题
计算机专业自荐信
2013/10/14 职场文书
电气工程自动化求职信
2014/03/14 职场文书
社会治安综合治理管理责任书
2014/04/16 职场文书
春节超市活动方案
2014/08/14 职场文书
2015年度党风廉政建设工作情况汇报
2015/01/02 职场文书
转变工作作风心得体会
2016/01/23 职场文书