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验证只能输入数字和一个小数点示例
Oct 21 Javascript
js对列表中第一个值处理与jsp页面对列表中第一个值处理的区别详解
Nov 05 Javascript
javascript实现依次输入input自动定焦
Dec 23 Javascript
Javascript仿新浪游戏频道鼠标悬停显示子菜单效果
Aug 21 Javascript
Sea.JS知识总结
May 05 Javascript
Bootstrap树形菜单插件TreeView.js使用方法详解
Nov 01 Javascript
JavaScript计时器用法分析【setTimeout和clearTimeout】
Jan 18 Javascript
详解windows下vue-cli及webpack 构建网站(二)导入bootstrap样式
Jun 17 Javascript
vue父组件向子组件(props)传递数据的方法
Jan 02 Javascript
webpack4简单入门实例
Sep 06 Javascript
vue 微信扫码登录(自定义样式)
Jan 06 Javascript
el-table树形表格表单验证(列表生成序号)
May 31 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
咖啡机如何保养和日常清洁?
2021/03/03 冲泡冲煮
PHP number_format() 函数定义和用法
2012/06/01 PHP
php一个解析字符串排列数组的方法
2015/05/12 PHP
php实现递归的三种基本方式
2020/07/04 PHP
谈谈你对Zend SAPIs(Zend SAPI Internals)的理解
2015/11/10 PHP
PHP命名空间与自动加载机制的基础介绍
2019/08/25 PHP
PHP程序守护进程化实现方法详解
2020/07/16 PHP
JS 自动完成 AutoComplete(Ajax 查询)
2009/07/07 Javascript
基于jQuery的为attr添加id title等效果的实现代码
2011/04/20 Javascript
jquery对象和DOM对象的区别介绍
2013/08/09 Javascript
jQuery中ajax的使用与缓存问题的解决方法
2013/12/19 Javascript
js如何获取兄弟、父类等节点
2014/01/06 Javascript
JS控制TreeView的结点选择
2016/11/11 Javascript
Node.JS中快速扫描端口并发现局域网内的Web服务器地址(80)
2017/09/18 Javascript
Vue-Router实现组件间跳转的三种方法
2017/11/07 Javascript
Vue自定义全局Toast和Loading的实例详解
2019/04/18 Javascript
vue通信方式EventBus的实现代码详解
2019/06/10 Javascript
详解JavaScript 的执行机制
2020/09/18 Javascript
[03:15]DOTA2-DPC中国联赛1月22日Recap集锦
2021/03/11 DOTA
python获取网页状态码示例
2014/03/30 Python
粗略分析Python中的内存泄漏
2015/04/23 Python
用python实现百度翻译的示例代码
2018/03/09 Python
python2.7实现爬虫网页数据
2018/05/25 Python
numpy数组之存取文件的实现示例
2019/05/24 Python
解析Python 偏函数用法全方位实现
2020/06/26 Python
Guess美国官网:美国知名服装品牌
2019/04/08 全球购物
建筑个人求职信范文
2014/01/25 职场文书
家居装修公司创业计划书范文
2014/03/20 职场文书
2014年大学生预备党员思想汇报1000字
2014/09/13 职场文书
房地产营销活动策划方案
2014/09/15 职场文书
未婚证明书模板
2014/10/08 职场文书
年底个人总结范文
2015/03/10 职场文书
解决Vue+SpringBoot+Shiro跨域问题
2021/06/09 Vue.js
如何用Python搭建gRPC服务
2021/06/30 Python
浅谈mysql哪些情况会导致索引失效
2021/11/20 MySQL
Smart 2 车辆代号 HC11 全新谍照曝光
2022/04/21 数码科技