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 相关文章推荐
JavaScript 大数据相加的问题
Aug 03 Javascript
小结Node.js中非阻塞IO和事件循环
Sep 18 Javascript
js Object2String方便查看js对象内容
Nov 24 Javascript
js实现的彩色方块飞舞奇幻效果
Jan 27 Javascript
通过修改360抢票的刷新频率和突破8车次限制实现方法
Jan 04 Javascript
JS图片压缩(pc端和移动端都适用)
Jan 12 Javascript
完美实现js选项卡切换效果(一)
Mar 08 Javascript
js实现省市级联效果分享
Aug 10 Javascript
JS中利用FileReader实现上传图片前本地预览功能
Mar 02 Javascript
Echarts实现多条折线可拖拽效果
Dec 19 Javascript
详细分析React 表单与事件
Jul 08 Javascript
jQuery实现回到顶部效果
Oct 19 jQuery
详解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
DISCUZ在win2003环境下 Unable to access ./include/common.inc.php in... 的问题终极解决方案
2011/11/21 PHP
Thinkphp3.2.3整合phpqrcode生成带logo的二维码
2016/07/21 PHP
jQuery TextBox自动完成条
2009/07/22 Javascript
JS 图片缩放效果代码
2010/06/09 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
jQuery实现dialog设置focus焦点的方法
2015/06/10 Javascript
AngularJS动态绑定HTML的方法分析
2016/11/07 Javascript
详解vue跨组件通信的几种方法
2017/06/15 Javascript
React Native预设占位placeholder的使用
2017/09/28 Javascript
vue2.0 兄弟组件(平级)通讯的实现代码
2018/01/15 Javascript
angular6的table组件开发的实现示例
2018/12/26 Javascript
Vue基础学习之项目整合及优化
2019/06/02 Javascript
js实现自定义滚动条的示例
2020/10/27 Javascript
Vue中避免滥用this去读取data中数据
2021/03/02 Vue.js
python 字典(dict)遍历的四种方法性能测试报告
2014/06/25 Python
Python smallseg分词用法实例分析
2015/05/28 Python
详解Python的Django框架中的模版继承
2015/07/16 Python
python 3.5实现检测路由器流量并写入txt的方法实例
2017/12/17 Python
python验证码识别实例代码
2018/02/03 Python
python计算列表内各元素的个数实例
2018/06/29 Python
Pandas 同元素多列去重的实例
2018/07/03 Python
详解python 模拟豆瓣登录(豆瓣6.0)
2019/04/18 Python
python爬虫 猫眼电影和电影天堂数据csv和mysql存储过程解析
2019/09/05 Python
在Python中字符串、列表、元组、字典之间的相互转换
2019/11/15 Python
PyQt5+python3+pycharm开发环境配置教程
2020/03/24 Python
Python代码执行时间测量模块timeit用法解析
2020/07/01 Python
Python爬虫爬取新闻资讯案例详解
2020/07/14 Python
如何用python实现一个HTTP连接池
2021/01/14 Python
HTML5不支持标签和新增标签详解
2016/06/27 HTML / CSS
Html5百叶窗效果的示例代码
2017/12/11 HTML / CSS
在canvas上实现元素图片镜像翻转动画效果的方法
2018/03/20 HTML / CSS
消防先进事迹材料
2014/02/10 职场文书
电气自动化求职信
2014/06/24 职场文书
教师培训简讯
2015/07/20 职场文书
PHP控制循环操作的时间
2021/04/01 PHP
mysql中DCL常用的用户和权限控制
2022/03/31 MySQL