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实现图片切换的幻灯片效果源代码
Dec 12 Javascript
使用jquery.upload.js实现异步上传示例代码
Jul 29 Javascript
jquery validate.js表单验证入门实例(附源码)
Nov 10 Javascript
jqueryMobile 动态添加元素,展示刷新视图的实现方法
May 28 Javascript
JavaScript的this关键字的理解
Jun 18 Javascript
微信小程序 出现47001 data format error原因解决办法
Mar 10 Javascript
javascript中this用法实例详解
Apr 06 Javascript
js,jq,css多方面实现简易下拉菜单功能
May 13 Javascript
在iframe中使bootstrap的模态框在父页面弹出问题
Aug 07 Javascript
Js利用Canvas实现图片压缩功能
Sep 13 Javascript
JavaScript实现求最大公共子串的方法
Feb 03 Javascript
解决webpack多页面内存溢出的方法示例
Oct 08 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
如何使用Linux的Crontab定时执行PHP脚本的方法
2011/12/19 PHP
php读取本地文件常用函数(fopen与file_get_contents)
2013/09/09 PHP
ThinkPHP CURD方法之data方法详解
2014/06/18 PHP
基于jquery的DIV随滚动条滚动而滚动的代码
2012/07/20 Javascript
jQuery学习笔记 更改jQuery对象
2012/09/19 Javascript
jsp+javascript打造级连菜单的实例代码
2013/06/14 Javascript
浅析JavaScript中的隐式类型转换
2013/12/05 Javascript
jQuery中:eq()选择器用法实例
2014/12/29 Javascript
基于jQuery实现歌词滚动版音乐播放器的代码
2016/09/17 Javascript
jquery 动态增加删除行的简单实例(推荐)
2016/10/12 Javascript
微信小程序 获取当前地理位置和经纬度实例代码
2016/12/05 Javascript
iscroll.js滚动加载实例详解
2017/07/18 Javascript
vue实现表格增删改查效果的实例代码
2017/07/18 Javascript
js实现掷骰子小游戏
2019/10/24 Javascript
JQuery事件委托(适用于给动态生成的脚本元素添加事件)
2020/02/01 jQuery
[02:36]DOTA2英雄基础教程 帕格纳
2014/01/20 DOTA
Python open读写文件实现脚本
2008/09/06 Python
python使用BeautifulSoup分页网页中超链接的方法
2015/04/04 Python
横向对比分析Python解析XML的四种方式
2016/03/30 Python
python3.6 +tkinter GUI编程 实现界面化的文本处理工具(推荐)
2017/12/20 Python
Python发展简史 Python来历
2019/05/14 Python
JupyterNotebook 输出窗口的显示效果调整实现
2020/09/22 Python
HTML5安全介绍之内容安全策略(CSP)简介
2012/07/10 HTML / CSS
免费获得微软MCSD证书赶快行动吧!
2012/11/13 HTML / CSS
Booking.com美国:全球酒店预订网站
2017/04/18 全球购物
Conforama瑞士:家具、厨房、电器、装饰
2020/09/06 全球购物
什么是会话Bean
2015/05/14 面试题
英文翻译的自我评价语句
2013/10/04 职场文书
工厂仓管员岗位职责
2014/01/01 职场文书
《绿色蝈蝈》教学反思
2014/03/02 职场文书
房屋转让协议书
2014/10/18 职场文书
专职安全员岗位职责
2015/04/11 职场文书
汽车车尾标语大全
2015/08/11 职场文书
八年级地理课件资料及考点知识分享
2019/08/30 职场文书
2019年手机市场的调研报告2篇
2019/10/10 职场文书
MySQL之DML语言
2021/04/05 MySQL