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  Error 对象 错误处理
May 18 Javascript
javascript 写类方式之八
Jul 05 Javascript
IE之动态添加DOM节点触发window.resize事件
Jul 27 Javascript
jquery下组织javascript代码(js函数化)
Aug 25 Javascript
JS动态增加删除UL节点LI及相关内容示例
May 21 Javascript
js改变Iframe中Src的方法
May 05 Javascript
jQuery实现淡入淡出二级下拉导航菜单的方法
Aug 28 Javascript
AngularJS基础教程之简单介绍
Sep 27 Javascript
JS中artdialog弹出框控件之提交表单思路详解
Apr 18 Javascript
微信小程序页面开发注意事项整理
May 18 Javascript
JavaScript+HTML5 canvas实现放大镜效果完整示例
May 15 Javascript
webpack安装配置与常见使用过程详解(结合vue)
Jun 01 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实现把url转换迅雷thunder资源下载地址的方法
2014/11/07 PHP
php类的定义与继承用法实例
2015/07/07 PHP
详解PHP实现定时任务的五种方法
2016/07/25 PHP
Laravel-添加后台模板AdminLte的实现方法
2019/10/08 PHP
漂亮的提示信息(带箭头)
2007/03/21 Javascript
Jquery创建一个层当鼠标移动到层上面不消失效果
2013/12/12 Javascript
jquery中push()的用法(数组添加元素)
2014/11/25 Javascript
深入理解JavaScript系列(43):设计模式之状态模式详解
2015/03/04 Javascript
Angular的MVC和作用域
2016/12/26 Javascript
JavaScript使用正则表达式获取全部分组内容的方法示例
2017/01/17 Javascript
VUE 更好的 ajax 上传处理 axios.js实现代码
2017/05/10 Javascript
JSON在Javascript中的使用(eval和JSON.parse的区别)详细解析
2017/09/05 Javascript
记一次webapck4 配置文件无效的解决历程
2018/09/19 Javascript
Vue插槽原理与用法详解
2019/03/05 Javascript
微信小程序如何调用新闻接口实现列表循环
2019/07/02 Javascript
使用xampp将angular项目运行在web服务器的教程
2019/09/16 Javascript
Vue-drag-resize 拖拽缩放插件的使用(简单示例)
2019/12/04 Javascript
jQuery实现的解析本地 XML 文档操作示例
2020/04/30 jQuery
[52:29]DOTA2上海特级锦标赛主赛事日 - 2 胜者组第一轮#3Secret VS OG第三局
2016/03/03 DOTA
wxPython之wx.DC绘制形状
2019/11/19 Python
完美解决Django2.0中models下的ForeignKey()问题
2020/05/19 Python
用python按照图像灰度值统计并筛选图片的操作(PIL,shutil,os)
2020/06/04 Python
Python爬虫headers处理及网络超时问题解决方案
2020/06/19 Python
Python结合百度语音识别实现实时翻译软件的实现
2021/01/18 Python
5分钟让你掌握css3阴影、倒影、渐变小技巧(小编推荐)
2016/08/15 HTML / CSS
AmazeUI 等分网格的实现示例
2020/08/25 HTML / CSS
瑞士图书网站:Weltbild.ch
2019/09/17 全球购物
学生发电厂实习自我鉴定
2013/09/22 职场文书
行政人事岗位职责
2014/03/17 职场文书
爱祖国爱家乡演讲稿
2014/09/02 职场文书
运动会报道稿300字
2014/10/02 职场文书
2015年医院护理部工作总结
2015/04/23 职场文书
Jupyter notebook 不自动弹出网页的解决方案
2021/05/21 Python
python爬虫之selenium库的安装及使用教程
2021/05/23 Python
MySQL库表名大小写的选择
2021/06/05 MySQL
Python实现智慧校园自动评教全新版
2021/06/18 Python