Angular19 自定义表单控件

1 需求

  当开发者需要一个特定的表单控件时就需要自己开发一个和默认提供的表单控件用法相似的控件来作为表单控件;自定义的表单控件必须考虑模型和视图之间的数据怎么进行交互

2 官方文档 -> 点击前往

  Angular为开发者提供了ControlValueAccessor接口来辅助开发者构建自定义的表单控件,开发者只需要在自定义表单控件类中实现ControlValueAccessor接口中的方法就可以实现模型和视图之间的数据交互

interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  setDisabledState(isDisabled: boolean)?: void
}

  2.1 writeValue  

writeValue(obj: any): void

    该方法用于将值写入到自定义表单控件中的元素;

    这个参数值(obj)是使用这个自定义表单控件的组件通过模板表单或者响应式表单的数据绑定传过来的;

    在自定义表单控件的类中只需要将这个值(obj)赋值给一个成员变量即可,自定义表单控件的视图就会通过属性绑定显示出来这个值

  2.2 registerOnChange

registerOnChange(fn: any): void

    自定义表单控件的数据发生变化时会触发registerOnChange方法,该方用于如何处理自定义表单控件数据的变化;

    registerOnChange方法接收的参数(fn)其实是一个方法,该方法负责处理变化的数据

    当自定义控件数据变化时就会自动调用fn执行的方法,但是通常的做法是自定义一个方法 propagateChange 让自定义的方法指向fn,这样当数据变化时只需要调用 propagateChange 就可以对变化的数据进行处理

  2.3 registerOnTouched

registerOnTouched(fn: any): void

    表单控件被触摸时会触发registerOnTouched方法,具体细节待更新......2018-1-31 11:18:33

  2.4 setDisabledState

setDisabledState(isDisabled: boolean)?: void

    待更新......2018-1-31 11:19:30

3 编程步骤

  3.1 创建自定义表单控件组件   

<div>
  <h4>当前计数为:{{countNumber}}</h4>
  <br />
  <div>
    <button md-icon-button (click)="onIncrease()">
      <span>增加</span>
      <md-icon>add</md-icon>
    </button>
    <span style="margin-left: 30px;"></span>
    <button md-icon-button (click)="onDecrease()">
      <span>减少</span>
      <md-icon>remove</md-icon>
    </button>
  </div>
</div>

HTML

import { Component, OnInit } from ‘@angular/core‘;
import { ControlValueAccessor } from ‘@angular/forms‘;

@Component({
  selector: ‘app-counter‘,
  templateUrl: ‘./counter.component.html‘,
  styleUrls: [‘./counter.component.scss‘]
})
export class CounterComponent implements OnInit {

  countNumber: number = 0;

  constructor() { }

  ngOnInit() {
  }

  onIncrease() {
    this.countNumber++;
  }

  onDecrease() {
    this.countNumber--;
  }

}

TS

    3.1.1 功能描述

      点击增加按钮时当前计数会增加1,点击减少按钮时当前计数会剪1

      

    3.1.2 直接在其他组件中使用时会报错

      

      报错信息如下:

        

        错误信息是说我们我们使用的组件<app-counter>还不是一个表单控件

  3.2 如何让<app-counter>组件变成一个表单控件组件

    3.2.1 实现 ControlValueAccessor 接口

      

export class CounterComponent implements OnInit, ControlValueAccessor {

  countNumber: number = 0;

  constructor() { }

  ngOnInit() {
  }

  onIncrease() {
    this.countNumber++;
  }

  onDecrease() {
    this.countNumber--;
  }

    /**将数据从模型传输到视图 */
  writeValue(obj: any): void {
  }

  /**将数据从视图传播到模型 */
  registerOnChange(fn: any): void {

  }

  registerOnTouched(fn: any): void {

  }

  setDisabledState?(isDisabled: boolean): void {

  }

}

    3.2.2 指定依赖信息providers

      

import { Component, OnInit, forwardRef } from ‘@angular/core‘;
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from ‘@angular/forms‘;

@Component({
  selector: ‘app-counter‘,
  templateUrl: ‘./counter.component.html‘,
  styleUrls: [‘./counter.component.scss‘],
  providers: [
    {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => CounterComponent),
        multi: true
    }
  ]
})
export class CounterComponent implements OnInit, ControlValueAccessor {

  countNumber: number = 0;

  constructor() { }

  ngOnInit() {
  }

  onIncrease() {
    this.countNumber++;
  }

  onDecrease() {
    this.countNumber--;
  }

    /**将数据从模型传输到视图 */
  writeValue(obj: any): void {
  }

  /**将数据从视图传播到模型 */
  registerOnChange(fn: any): void {

  }

  registerOnTouched(fn: any): void {

  }

  setDisabledState?(isDisabled: boolean): void {

  }

}

    3.2.3 待修复bug

      虽然可以正常运行,但是表单控件中的元素接受不到使用表单控件那个组件中表单模型传过来的数据,表单控件变化的数据也无法回传到使用表单控件那个组件中的表单模型中去;简而言之,就是模型和视图之间无法进行数据交互

  3.3 实习那模型和试图的数据交互

    3.3.1 模型到视图

      重构自定义表单控件类中的 writeValue 方法

      技巧01:writeValue 方法中的参数是使用自定义表单控件的那个组件通过表单的数据绑定传进来的

        

    3.3.2 视图到模型

      》自定义一个方法来处理自定义表单控件中的变化数据    

propagateChange = (_: any) => {};

      》重构自定义表单控件类中的 registerOnChange 方法

  /**将数据从视图传播到模型 */
  registerOnChange(fn: any): void {
    this.propagateChange = fn;
  }

      》在数据变化的地方调用那个自定义的方法

        

  3.4 自定义表单控件组件代码汇总

<div>
  <h4>当前计数为:{{countNumber}}</h4>
  <br />
  <div>
    <button md-icon-button (click)="onIncrease()">
      <span>增加</span>
      <md-icon>add</md-icon>
    </button>
    <span style="margin-left: 30px;"></span>
    <button md-icon-button (click)="onDecrease()">
      <span>减少</span>
      <md-icon>remove</md-icon>
    </button>
  </div>
</div>

HTML

import { Component, OnInit, forwardRef } from ‘@angular/core‘;
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from ‘@angular/forms‘;

@Component({
  selector: ‘app-counter‘,
  templateUrl: ‘./counter.component.html‘,
  styleUrls: [‘./counter.component.scss‘],
  providers: [
    {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => CounterComponent),
        multi: true
    }
  ]
})
export class CounterComponent implements OnInit, ControlValueAccessor {

  countNumber: number = 0;

  propagateChange = (_: any) => {};

  constructor() { }

  ngOnInit() {
  }

  onIncrease() {
    this.countNumber++;
    this.propagateChange(this.countNumber);
  }

  onDecrease() {
    this.countNumber--;
    this.propagateChange(this.countNumber);
  }

  /**将数据从模型传输到视图 */
  writeValue(obj: any): void {
    this.countNumber = obj;
  }

  /**将数据从视图传播到模型 */
  registerOnChange(fn: any): void {
    /**fn其实是一个函数,当视图中的数据改变时就会调用fn指向的这个函数,从而达到将数据传播到模型的目的 */
    this.propagateChange = fn;  // 将fn的指向赋值给this.propagateChange,在需要将改变的数据传到模型时只需要调用this.propagateChange方法即可
  }

  registerOnTouched(fn: any): void {

  }

  setDisabledState?(isDisabled: boolean): void {

  }

}

TS

  3.5 使用自定义表单控件的那个组件的代码汇总

    技巧01:如果自定义表单控件和使用自定义表单控件的组件都在不在同一个模块时需要对自定义表单控件对应组件进行导出和导入操作

    

<div class="panel panel-primary">
  <div class="panel-heading">面板模板</div>
  <div class="panel-body">
    <h3>面板测试内容</h3>
  </div>
  <div class="panel-footer">2018-1-22 10:22:20</div>
</div>

<div class="panel-primary">
  <div class="panel-heading">自定义提取表单控件</div>
  <div class="panel-body">
    <form #myForm=ngForm>
      <app-counter name="counter" [(ngModel)]="countNumber">
      </app-counter>
    </form>
    <h6>绿线上是自定义提取的表单控件显示的内容</h6>
    <hr style="border: solid green 2px" />
    <h6>绿线下是使用自定义表单控件时表单的实时数据</h6>
    <h3>表单控件的值为:{{myForm.value | json}}</h3>
  </div>
  <div class="panel-footer">2018-1-31 10:09:17</div>
</div>

<div class="panel-primary">
  <div class="panel-heading">提取表单控件</div>
  <div class="panel-body">
    <form #form="ngForm">
      <p>outerCounterValue value: {{outerCounterValue}}</p>
      <app-exe-counter name="counter" [(ngModel)]="outerCounterValue"></app-exe-counter>
      <br />
      <button md-raised-button type="submit">Submit</button>
      <br />
      <div>
        {{form.value | json}}
      </div>
    </form>
  </div>
  <div class="panel-footer">2018-1-27 21:51:45</div>
</div>

<div class="panel panel-primary">
  <div class="panel-heading">ngIf指令测试</div>
  <div class="panel-body">
    <button md-rasied-button (click)="onChangeNgifValue()">改变ngif变量</button>
    <br />
    <div *ngIf="ngif; else ngifTrue" >
      <h4 style="background-color: red; color: white" >ngif变量的值为true</h4>
    </div>
    <ng-template #ngifTrue>
      <h4 style="background-color: black; color: white">ngif变量的值为false</h4>
    </ng-template>
  </div>
  <div class="panel-footer">2018-1-27 16:58:17</div>
</div>

<div class="panel panel-primary">
  <div class="panel-heading">RXJS使用</div>
  <div class="panel-body">
    <h4>测试内容</h4>
  </div>
  <div class="panel-footer">2018-1-23 21:14:49</div>
</div>

<div class="panel panel-primary">
  <div class="panel-heading">自定义验证器</div>
  <div class="panel-body">
    <form (ngSubmit)="onTestLogin()" [formGroup]="loginForm">
      <md-input-container>
        <input mdInput placeholder="请输入登录名" formControlName="username" />
      </md-input-container>
      <br />
      <md-input-container>
        <input mdInput placeholder="请输入密码" formControlName="userpwd" />
      </md-input-container>
      <br />
      <button type="submit" md-raised-button>登陆</button>
    </form>
  </div>
  <div class="panel-footer">2018-1-23 11:06:01</div>
</div>

<div class="panel panel-primary">
  <div class="panel-heading">响应式表单</div>
  <div class="panel-body">
    <form [formGroup]="testForm">
      <md-input-container>
        <input mdInput type="text" placeholder="请输入邮箱" formControlName="email" />
        <span mdSuffix>@163.com</span>
      </md-input-container>
      <br />
      <md-input-container>
        <input mdInput type="password" placeholder="请输入密码" formControlName="password" />
      </md-input-container>
    </form>
    <hr />
    <div>
      <h2>表单整体信息如下:</h2>
      <h4>表单数据有效性:{{testForm.valid}}</h4>
      <h4>表单数据为:{{testForm.value | json}}</h4>
      <h4>获取单个或多个FormControl:{{testForm.controls[‘email‘] }}</h4>
      <hr />
      <h2>email输入框的信息如下:</h2>
      <h4>有效性:{{testForm.get(‘email‘).valid}}</h4>
      <h4>email输入框的错误信息为:{{testForm.get(‘email‘).errors | json}}</h4>
      <h4>required验证结果:{{testForm.hasError(‘required‘, ‘email‘) | json}}</h4>
      <h4>minLength验证结果:{{ testForm.hasError(‘minLength‘, ‘email‘) | json }}</h4>
      <h4>hello:{{ testForm.controls[‘email‘].errors | json }}</h4>
      <hr />
      <h2>password输入框啊的信息如下:</h2>
      <h4>有效性:{{testForm.get(‘password‘).valid}}</h4>
      <h4>password输入框的错误信息为:{{testForm.get(‘password‘).errors | json }}</h4>
      <h4>required验证结果:{{testForm.hasError(‘required‘, ‘password‘) | json}}</h4>
    </div>
    <div>
      <button nd-rasied-button (click)="onTestClick()">获取数据</button>
      <h4>data变量:{{data}}</h4>
    </div>
  </div>
  <div class="panel-footer">2018-1-22 15:58:43</div>
</div> 

<div class="panel panel-primary">
  <div class="panel-heading">利用响应式编程实现表单元素双向绑定</div>
  <div class="panel-body">
    <md-input-container>
      <input mdInput  placeholder="请输入姓名(响应式双向绑定):" [formControl]="name"/>
    </md-input-container>
    <div>
      姓名为:{{name.value}}
    </div>
  </div>
  <div class="panel-footer">2018-1-22 11:12:35</div>
</div> -->

<div class="panel panel-primary">
  <div class="panel-heading">模板表单</div>
  <div class="panel-body">
    <md-input-container>
      <input mdInput placeholder="随便输入点内容" #a="ngModel" [(ngModel)]="desc" name="desc" />
      <button type="button" md-icon-button mdSuffix (click)="onTestNgModelClick()">
        <md-icon>done</md-icon>
      </button>
    </md-input-container>
    <div>
      <h3>名为desc的表单控件的值为:{{ a.value }}</h3>
    </div>
  </div>
  <div class="panel-footer">2018-1-22 10:19:31</div>
</div>

<div class="panel panel-primary">
  <div class="panel-heading">md-chekbox的使用</div>
  <div calss="panel-body">
    <div>
        <md-checkbox #testCheckbox color="primary" checked="true">测试</md-checkbox>
    </div>
    <div *ngIf="testCheckbox.checked">
      <h2>测试checkbox被选中啦</h2>
    </div>
  </div>
  <div class="panel-footer">2018-1-18 14:02:20</div>
</div>  

<div class="panel panel-primary">
  <div class="panel-heading">md-tooltip的使用</div>
  <div class="panel-body">
    <span md-tooltip="重庆火锅">鼠标放上去</span>
  </div>
  <div class="panel-footer">2018-1-18 14:26:58</div>
</div>

<div class="panel panel-primary">
  <div class="panel-heading">md-select的使用</div>
  <div class="panel-body">
    <md-select placeholder="请选择目标列表" class="fill-width" style="height: 40px;">
      <md-option *ngFor="let taskList of taskLists" [value]="taskList.name">{{taskList.name}}</md-option>
    </md-select>
  </div>
  <div class="panel-footer">2018-1-18 14:26:58</div>
</div>  

<div class="panel panel-primary">
  <div class="panel-heading">ngNonBindable指令的使用</div>
  <div class="panel-body">
    <h3>描述</h3>
    <p>使用了ngNonBindable的标签,会将该标签里面的元素内容全部都看做时纯文本</p>
    <h3>例子</h3>
    <p>
      <span>{{taskLists | json }}</span>
      <span ngNonBindable>&larr; 这是{{taskLists | json }}渲染的内容</span>
    </p>
  </div>
  <div class="panel-footer">2018-1-19 09:34:26</div>
</div>

HTML

    

import { Component, OnInit, HostListener, Inject} from ‘@angular/core‘;
import { FormControl, FormGroup, FormBuilder, Validators } from ‘@angular/forms‘;
import { Http } from ‘@angular/http‘;
import { QuoteService } from ‘../../service/quote.service‘;

@Component({
  selector: ‘app-test01‘,
  templateUrl: ‘./test01.component.html‘,
  styleUrls: [‘./test01.component.scss‘]
})
export class Test01Component implements OnInit {

  countNumber: number = 9;

  outerCounterValue: number = 5;  

  ngif = true;

  loginForm: FormGroup;

  testForm: FormGroup;
  data: any; 

  name: FormControl = new FormControl();

  desc: string = ‘hello boy‘;

  taskLists = [
    {label: 1, name: ‘进行中‘},
    {label: 2, name: ‘已完成‘}
  ];

  constructor(
    private formBuilder: FormBuilder,
    private http: Http,
    @Inject(‘BASE_CONFIG‘) private baseConfig,
    private quoteService: QuoteService
  ) {}

  ngOnInit() {
    this.testForm = new FormGroup({
      email: new FormControl(‘‘, [Validators.required, Validators.minLength(4)], []),
      password: new FormControl(‘‘, [Validators.required], [])
    });

    this.name.valueChanges
    .debounceTime(500)
    .subscribe(value => alert(value));

    this.loginForm = this.formBuilder.group({
      username: [‘‘, [Validators.required, Validators.minLength(4), this.myValidator], []],
      userpwd: [‘‘, [Validators.required, Validators.minLength(6)], []]
    });

    this.quoteService.test()
    .subscribe(resp => console.log(resp));

  }

  onChangeNgifValue() {
    if (this.ngif == false) {
      this.ngif = true;
    } else {
      this.ngif = false;
    }
  }

  @HostListener(‘keyup.enter‘)
  onTestNgModelClick() {
    alert(‘提交‘);

  }

  onTestClick() {
    // this.data = this.testForm.get(‘email‘).value;
    // console.log(this.testForm.getError);
    console.log(this.testForm.controls[‘email‘]);
  }

  onTestLogin() {
    console.log(this.loginForm.value);
    if (this.loginForm.valid) {
      console.log(‘登陆数据合法‘);
    } else {
      console.log(‘登陆数据不合法‘);
      console.log(this.loginForm.controls[‘username‘].errors);
      console.log(this.loginForm.get(‘userpwd‘).errors);
    }
  }

  myValidator(fc: FormControl): {[key: string]: any} {
    const valid = fc.value === ‘admin‘;
    return valid ? null : {myValidator: {requiredUsername: ‘admin‘, actualUsername: fc.value}};
  }

}

TS

  3.6 初始化效果展示

    

    

原文地址:https://www.cnblogs.com/NeverCtrl-C/p/8391533.html

时间: 2024-11-05 16:04:48

Angular19 自定义表单控件的相关文章

AngularJS自定义表单控件

<!doctype html> <html ng-app="myApp"> <head> <script src="G:\\Source\\Repos\\GWD\\Gridsum.WebDissector.Website.ZC\\Gridsum.WebDissector.Website.ZC\\Pages\\dist\\assets\\lib\\angularjs\\angular.js"></script>

vue表单控件绑定+自定义组件

vue表单控件绑定 v-model 在表单控件元素上创建双向数据绑定 文本框双向绑定 多选框演示 下拉列表演示 vue自定义组件 组件放在components目录下 组件基本要素:props  $emit 通过import导入自定义组件 制作一个倒计时组件: 1.在conponents目录下,新建一个time.vue 方法写在mouted声明周期函数内,代码如下: 然后在index.vue中使用组件: 我之前组件命名为time,可能与默认什么冲突了,然后报错不让用,所以改名成cyytime 但是

仿苹果电脑任务栏菜单&amp;&amp;拼图小游戏&amp;&amp;模拟表单控件

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-

html新的语义化标签和表单控件

语义化标签 1 <!DOCTYPE html> 2 <html> 3 <head lang="zh-cn"> 4 <meta charset="UTF-8"> 5 <title>新增的语义化标签</title> 6 </head> 7 <body> 8 <header>页面头部或某个板块的头部</header> 9 <footer>页面

分针网—每日分享:Vue.js事件处理器与表单控件绑定

事件处理主要通过v-on这个指令来执行. 事件监听及方法处理 1.简单的可以直接内嵌在页面. 2.可以通过将方法定义在methods中,然后再v-on中执行 3.可以通过绑定给函数传递参数,还可以传递通过变量$event给函数传递原生DOM事件. <div id="app-1"> <button v-on:click="counter += 1">增加1</button> <p>这个按钮被点击了{{counter}}&

vue.js基础知识篇(1):简介、数据绑定、指令、计算属性、表单控件绑定和过滤器

目录第一章:vue.js是什么? 代码链接: http://pan.baidu.com/s/1qXCfzRI 密码: 5j79 第一章:vue.js是什么? 1.vue.js是MVVM框架 MVVM的代表框架是Angular.js,以及vue.js. MVVM的view和model是分离的,View的变化会自动更新到ViewModel上,ViewModel的变化会自动同步到View上显示.这种自动同步依赖于ViewModel的属性实现了Observer. 2.它与angular.js的区别 相同

html入门(列表、表单、常用表单控件、浮动框架、iframe、 摘要与细节、度量标签)

一.列表 1.作用:默认显示方式为从上到下的显示数据 2.列表的组成 列表类型和列表项 3.列表的分类:有序列表   无序列表   自定义列表 无序列表语法为ul>li, 语法:ul代表列表,li代表列表项 有序列表语法为ol>li, 语法:ol代表列表,li代表列表项 自定义列表,用法: <dl> <dt>1</dt> <dd>1</dd> </dl> 语法: dl列表的类型 dt列表的标题 dd 列表项 二.表单 1.

5.9 HTML5 新增表单控件 ---不是特别重要

HTML5 新增表单控件新增类型:网址 邮箱 日期 时间 星期 数量 范围 电话 颜色 搜索这些控件基本不使用:因为在不同的浏览器中效果不一样.产品设计中不允许多个浏览器效果不同,必须统一. <label>网址:</label><input type="url" name="" required><br><br> <label>邮箱:</label><input type=&q

vue2.0 之表单控件绑定

表单控件绑定v-model 1.文本 <template> <div> <input type="text" name="" v-model="myVal"><br/> {{ myVal }}<br/> <input type="text" name="" v-model.lazy="myVal1"><br/&