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> <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