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 相关文章推荐
圣诞节Merry Christmas给博客添加浪漫的下雪效果基于jquery实现
Dec 27 Javascript
jquery 页面滚动到底部自动加载插件集合
Jan 31 Javascript
JS常用表单验证方法总结
May 22 Javascript
JS替换字符串中字符即替换全部而不是第一个
Jun 04 Javascript
jquery实现弹出层登录和全屏层注册特效
Aug 28 Javascript
基于jQuey实现鼠标滑过变色(整行变色)
Dec 07 Javascript
详解AngularJS中自定义过滤器
Dec 28 Javascript
JS实现的在线调色板实例(附demo源码下载)
Mar 01 Javascript
JS简单循环遍历json数组的方法
Apr 22 Javascript
使用JavaScript判断手机浏览器是横屏还是竖屏问题
Aug 02 Javascript
JavaScript第一篇之实现按钮全选、功能
Aug 21 Javascript
Extjs表单输入框异步校验的插件实现方法
Mar 20 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小技巧搜集,每个PHPer都来露一手
2007/01/02 PHP
解析dedecms空间迁移步骤详解
2013/05/15 PHP
推荐30个新鲜出炉的精美 jQuery 效果
2012/03/26 Javascript
表单类各种类型(文本框)失去焦点效果jquery代码
2013/04/26 Javascript
JS通过分析userAgent属性来判断浏览器的类型及版本
2014/03/28 Javascript
使用jQuery中的when实现多个AJAX请求对应单个回调的例子分享
2014/04/23 Javascript
封装了一个支持匿名函数的Javascript事件监听器
2014/06/05 Javascript
javascript实现漂亮的拖动层,窗口拖拽特效
2015/04/24 Javascript
通过网页查看JS源码中汉字显示乱码的解决方法
2016/10/26 Javascript
jquery easyui validatebox remote的使用详解
2016/11/09 Javascript
javascript 利用arguments实现可变长参数
2016/11/21 Javascript
weUI应用之JS常用信息提示弹层的封装
2016/11/21 Javascript
js实现可输入可选择的select下拉框
2016/12/21 Javascript
JS正则表达式验证中文字符
2017/05/08 Javascript
jQuery操作DOM_动力节点Java学院整理
2017/07/04 jQuery
使用原生js封装的ajax实例(兼容jsonp)
2017/10/12 Javascript
Elasticsearch实现复合查询高亮结果功能
2019/09/10 Javascript
element form 校验数组每一项实例代码
2019/10/10 Javascript
详解webpack的clean-webpack-plugin插件报错
2020/10/16 Javascript
利用Python自带PIL库扩展图片大小给图片加文字描述的方法示例
2017/08/08 Python
Python魔法方法功能与用法简介
2019/04/04 Python
使用matplotlib绘制图例标签中带有公式的图
2019/12/13 Python
Python count函数使用方法实例解析
2020/03/23 Python
利用Python如何制作贪吃蛇及AI版贪吃蛇详解
2020/08/24 Python
html5 worker 实例(一) 为什么测试不到效果
2013/06/24 HTML / CSS
荷兰男士时尚网上商店:Suitable
2017/12/25 全球购物
美国50岁以上单身人士约会平台:SilverSingles
2018/06/29 全球购物
Sneaker Studio罗马尼亚网站:购买运动鞋
2018/11/04 全球购物
美国汽车零部件和配件网站:CarParts
2019/03/13 全球购物
为什么要用EJB
2014/04/17 面试题
机关门卫岗位职责
2013/12/30 职场文书
城市规划应届毕业生自荐信
2014/07/04 职场文书
2015年环卫处个人工作总结
2015/07/27 职场文书
创业者如何撰写出一份打动投资人的商业计划书?
2019/07/02 职场文书
python单元测试之pytest的使用
2021/06/07 Python
Nginx 502 bad gateway错误解决的九种方案及原因
2022/08/14 Servers