前言
前端框架的强大无疑给开发者省去了不少烦恼,又因比较完善的UI库支撑,让部分后端开发者能够省去大量样式设计的时间成本,纵然如此,业务的多变性是框架本身无法预料的,很多的控件功能在实际开发中总是不够完善和灵活,所以需要开发者结合业务需求进行再次封装这些UI控件/组件。
表单控件
常规组件只需要根据官方指引,写好数据传输的方式和订阅即可任意使用,表单控件有点特殊,按照常规方式写出来的组件使用在表单中,绑定ngModel或者formControlName,随之而来的是一个报错:
RROR Error: No value accessor for form control with name: 'userName'
ControlValueAccessor
Defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM
只有实现了这个接口才可以完成像普通表单元素那样使用和验证。
interface ControlValueAccessor { writeValue(obj: any): void registerOnChange(fn: any): void registerOnTouched(fn: any): void setDisabledState(isDisabled: boolean)"htmlcode">export class MyInputComponent implements OnInit, ControlValueAccessor { value: string | number; @Input() disabled: boolean; @Input() placeholder: string; @Input() type = 'text'; constructor() { } ngOnInit() { } writeValue(data: any) { this.value = data; } registerOnChange(fn: any) { } registerOnTouched(fn: any) { } setDisabledState(disabled: boolean) { this.disabled = disabled; } }其实封装工作只完成一半,组件装饰器元数据完整:
@Component({ // tslint:disable-next-line: component-selector selector: 'my-input', templateUrl: './my-input.component.html', styleUrls: ['./my-input.component.scss'], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MyInputComponent), multi: true }] })至此,控件在form表单中使用不会报错;表单内放置一个查询按钮,用来输出表单状态:
<form nz-form [formGroup]="form" (ngSubmit)="submit(form)"> <div nz-row nzFlex [nzGutter]="8"> <div nz-col [nzSpan]="6"> <nz-form-item> <nz-form-label [nzSpan]="10">userName</nz-form-label> <nz-form-control [nzSpan]="14"> <my-input formControlName="userName"></my-input> </nz-form-control> </nz-form-item> </div> </div> <button nz-button type="submit" [nzType]="'primary'">查询</button> </form>ngOnInit() { this.form = this.fb.group({ userName: [2] }); } submit(form: FormGroup) { console.log(form); }封装控件内部:
<div class="my-input"> <input type="{{type}}" value="{{value}}" placeholder="{{placeholder}}"> </div>通过formControlName的绑定方式将userName传入控件,控件通过writeValue方法接收并赋值到自身属性value,用于与原生input交互,此时我们手动输入内容为数字3,然后打印:
可以看到表单没有获取到最新的值,这是因为目前位置表单获取组件的value还是初始值,我们也没有提供改变value的方法机制,修改html:
<div class="my-input"> <input type="{{type}}" [ngModel]="actualValue" placeholder="{{placeholder}}" (ngModelChange)="modelChange($event)"> </div>这里稍微解释input绑定数据与触发的更新方法可以选择原生的value和input进行更新,也可以选择ng提供的ngModel和ngModelChange事件更新控件,区别在于使用原生input的输入事件,要使用到事件对象展开找到元素的value属性值;而使用ng官方框架自带的事件,事件对象$event就是最新的value值。
新增set value方法:
set value(data) { this.actualValue = data; // 通知表单value更新 this.onChange(data); } registerOnChange(fn: any) { // 注册表单的value改变通知方法 this.onChange = fn; } modelChange(event) { this.value = event; }输入 3 ,查询打印:
实现原生input基础属性
这个几乎是一条默认的规则,封装的控件至少实现原生input的基础属性功能,在此基础上再进行满足业务需求。
- type
- maxlength
- minlength
- placeholder
- ......
这里只讨论type为text和number的情况,radio等其它类型没必要深入。
我们不能直接使用maxlength进行与input绑定,至少写法不是很好,比较妥善的做法是动态的判断长度值,并且将正确的值设置到原生input属性中。
为此修改html:
<div class="my-input"> <input type="{{type}}" #inputElement [(ngModel)]="actualValue" placeholder="{{placeholder}}" (ngModelChange)="modelChange($event)" > </div>注入 Renderer2,用于对原生元素操作
ngOnChanges(changes: SimpleChanges) { this.initAttributes(changes); } initAttributes(changes: SimpleChanges) { for (const key in changes) { if (changes.hasOwnProperty(key)) { const element = changes[key]; if (element) { this.render2.setProperty(this.inputElement.nativeElement, key, element.currentValue); } } } }Validator
ngOnInit() { this.form = this.fb.group({ userName: [2, [Validators.required, Validators.minLength(3)]] }); }经过打印测试,表单的状态正确 √
适当使用指令
假如此时需要对输入内容拦截处理,目前在不写input事件的情况下无法做到,假如针对一个type=number类型的输入框,设置最大值,超过这个值不会改变,原生input元素确实有max属性支撑验证,但是它无法改变value值,也就是说假如这个最大值不是必要验证属性,那么表单还是可以提交最新的超出值,用指令可以拦截处理。
import { Directive, ElementRef, HostListener, Renderer2, Input } from '@angular/core'; @Directive({ selector: '[appInput]', }) export class InputDirective { constructor( private el: ElementRef, private render: Renderer2 ) { // 添加预设class render.addClass(this.el.nativeElement, 'my-input'); } @HostListener('input') onInputChange() { const element = this.el.nativeElement; if (element.max && Number(element.value) >= Number(element.max)) { this.render.setProperty(element, 'value', element.max); } } }<div class="my-input"> <input appInput #inputElement [(ngModel)]="actualValue" placeholder="{{placeholder}}" (ngModelChange)="modelChange($event)" > </div><my-input formControlName="userName" [maxLength]="5" [type]="'number'" [max]="250"></my-input>表单验证测试:
form表单拿到的值还是输入的非法值,这是因为模型值与原生元素之间没有真正的做到统一一致,
指令中核心代码修改:
@Output() valueChange = new EventEmitter(); @HostListener('input') onInputChange() { const element = this.el.nativeElement; if (element.max && Number(element.value) >= Number(element.max)) { this.render.setProperty(element, 'value', element.max); this.valueChange.emit(element.value); } }在input 标签上添加事件监听 (valueChange)="onValueChange($event)"
onValueChange(event) { this.modelChange(event); }表单获取的值与原生控件的value一致,一般自行封装原生控件还需要加入自己的样式,甚至有时候我们封装的主要目的就是美化样式,动态添加class示例:
@Directive({ selector: '[appInput]', // tslint:disable-next-line: no-host-metadata-property host: { '[class.my-input-disabled]': 'disabled' } }) export class InputDirective { constructor( private el: ElementRef, private render: Renderer2 ) { // 添加预设class render.addClass(this.el.nativeElement, 'my-input'); } @Input() @InputBoolean() disabled = false; @Output() valueChange = new EventEmitter(); @HostListener('input') onInputChange() { const element = this.el.nativeElement; if (element.max && Number(element.value) >= Number(element.max)) { this.render.setProperty(element, 'value', element.max); this.valueChange.emit(element.value); } console.log(element.value); } }结尾:总结下封装表单控件的原则:
1.原生控件支持的属性机制理论上需要全部保留实现(特别针对某业务封装除外);
2.不涉及复杂的数据处理、判断等逻辑的优先使用指令处理,例如本例中input的大多数功能都可以不做封装,原生标签input已经很完善;
3.get和set方法必须体现,且要保持模型数据与原生元素的value一致,外部操作可以更改组件属性,是否需要监听属性变化作出相应处理根据空间类型和业务进行斟酌;
4.一定要使用form表单提交功能去验证,原生form 配合name和label
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]