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 相关文章推荐
IE6与IE7中,innerHTML获取param的区别
Mar 15 Javascript
玩转jQuery按钮 请告诉我你最喜欢哪些?
Jan 08 Javascript
JQuery.closest(),parent(),parents()寻找父结点
Feb 17 Javascript
Javascript 键盘事件的组合使用实现代码
May 04 Javascript
javascript放大镜效果的简单实现
Dec 09 Javascript
node.js中的events.emitter.removeListener方法使用说明
Dec 10 Javascript
JS简单封装的图片无缝滚动效果示例【测试可用】
Mar 22 Javascript
jQuery基于Ajax实现读取XML数据功能示例
May 31 jQuery
利用JavaScript将Excel转换为JSON示例代码
Jun 14 Javascript
Weex开发之地图篇的具体使用
Oct 16 Javascript
Vue2.X和Vue3.0数据响应原理变化的区别
Nov 07 Javascript
JavaScript进阶(一)变量声明提升实例分析
May 09 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
Yii2超好用的日期和时间组件(值得收藏)
2016/05/05 PHP
php PDO异常处理详解
2016/11/20 PHP
PHP实现双链表删除与插入节点的方法示例
2017/11/11 PHP
js 深拷贝函数
2008/12/04 Javascript
JavaScript 闭包深入理解(closure)
2009/05/27 Javascript
javascript中&quot;/&quot;运算符常见错误
2010/10/13 Javascript
JS Jquery 遍历,筛选页面元素 自动完成(实现代码)
2013/07/08 Javascript
jquery实现键盘左右翻页特效
2015/04/30 Javascript
jquery实现鼠标拖拽滑动效果来选择数字的方法
2015/05/04 Javascript
jQuery实现选项联动轮播效果【附实例】
2016/04/19 Javascript
Bootstrap学习笔记之css样式设计(1)
2016/06/07 Javascript
原生js仿淘宝网商品放大镜效果
2017/02/28 Javascript
JS 正则表达式验证密码、邮箱格式的实例代码
2018/10/28 Javascript
解决vue更新路由router-view复用组件内容不刷新的问题
2019/11/04 Javascript
[46:49]完美世界DOTA2联赛PWL S3 access vs Rebirth 第二场 12.19
2020/12/24 DOTA
python3.4用函数操作mysql5.7数据库
2017/06/23 Python
python版微信跳一跳游戏辅助
2018/01/11 Python
Python多进程并发与多线程并发编程实例总结
2018/02/08 Python
Python3显示当前时间、计算时间差及时间加减法示例代码
2019/09/07 Python
Django实现简单网页弹出警告代码
2019/11/15 Python
python将时分秒转换成秒的实例
2019/12/07 Python
Python StringIO如何在内存中读写str
2020/01/07 Python
Python reversed函数及使用方法解析
2020/03/17 Python
Python实现密钥密码(加解密)实例详解
2020/04/26 Python
Python Tkinter实例——模拟掷骰子
2020/10/24 Python
Grow Gorgeous美国官网:只要八天,体验唤醒毛囊后新生的茂密秀发
2018/06/04 全球购物
国际旅客访问北美最大的汽车租赁提供商:Alamo Rent A Car
2018/06/13 全球购物
Spartoo美国:欧洲排名第一的在线时装零售商
2019/12/12 全球购物
优秀学生干部推荐材料
2014/02/03 职场文书
2014年语文教学工作总结
2014/12/17 职场文书
构建和谐校园倡议书
2015/01/19 职场文书
在职证明格式样本
2015/06/15 职场文书
会议主持词结束语
2015/07/03 职场文书
nginx配置虚拟主机的详细步骤
2021/07/21 Servers
解析redis hash应用场景和常用命令
2021/08/04 Redis
JS前端监控采集用户行为的N种姿势
2022/07/23 Javascript