Angular7表单控件验证问题:valid属性值打印结果不一致
Angular 7表单控件valid属性显示不一致问题排查与解决
这问题我之前也碰到过!核心原因是你用了异步验证器,而读取valid属性的时机不对——异步验证还没完成,你就立刻去取值了,拿到的是验证前的旧状态;但浏览器打印完整控件对象时,会延迟解析对象的属性,等异步验证跑完后才显示最新的valid值,所以就出现了看起来矛盾的情况。
为什么会这样?
你的mimeType验证器是返回Observable的异步验证器,它依赖FileReader读取文件头,这个操作是异步的。当你调用updateValueAndValidity()后,Angular会触发异步验证,但不会等待它完成就继续执行后面的代码,所以console.log(control.valid)拿到的是验证开始前的旧值;而打印整个控件对象时,浏览器的控制台会在你展开对象时才去读取最新的属性值,这时候异步验证已经完成了,所以看到的valid是正确的。
正确检查控件有效性的方法
要拿到异步验证后的真实valid状态,你需要等待异步操作完成,这里有两种实用的方式:
方法1:订阅控件的statusChanges流
通过监听控件的状态变化,等状态稳定后再获取valid值:
import { filter, first } from 'rxjs/operators'; // ... onDocPicked(event: Event, type: string) { const file = (event.target as HTMLInputElement).files[0]; // 先获取目标控件,避免重复调用get() const docControl = this.customerForm.get('docs').get(type); docControl.patchValue(file); docControl.updateValueAndValidity(); docControl.markAsDirty(); // 订阅状态变化,只取第一次稳定的VALID/INVALID状态 docControl.statusChanges.pipe( filter(status => status === 'VALID' || status === 'INVALID'), first() // 拿到结果后自动取消订阅,避免内存泄漏 ).subscribe(status => { console.log('验证完成后的valid值:', docControl.valid); // 这里可以做后续的逻辑处理,比如提交表单或提示用户 }); }
方法2:在模板中用async管道绑定状态
如果需要在页面上展示验证结果,可以直接用async管道绑定控件的状态流,Angular会自动处理异步更新:
<!-- 假设你的控件类型是idCard --> <div class="validation-message"> 当前控件状态:{{ customerForm.get('docs').get('idCard').statusChanges | async }} 是否有效:{{ customerForm.get('docs').get('idCard').valid }} </div>
额外小优化
- 尽量避免重复调用
this.customerForm.get('docs').get(type),可以像上面那样把控件赋值给变量,提高代码可读性和性能。 - 你的异步验证器逻辑是没问题的,不过可以考虑用
new Observable()替代Observable.create()(后者在RxJS 6+中已经不推荐使用了),调整后的验证器代码如下:
export const mimeType = (control: AbstractControl): Observable<{ [key: string]: any } | null> => { if (!control.value || typeof control.value === 'string') { return of(null); } const file = control.value as File; return new Observable((observer: Observer<{ [key: string]: any } | null>) => { const fileReader = new FileReader(); fileReader.addEventListener('loadend', () => { const arr = new Uint8Array(fileReader.result as ArrayBuffer).subarray(0, 4); let header = ''; let isValid = false; for (let i = 0; i < arr.length; i++) { header += arr[i].toString(16); } switch (header) { case '89504e47': // PNG case 'ffd8ffe0': case 'ffd8ffe1': case 'ffd8ffe2': case 'ffd8ffe3': case 'ffd8ffe8': // JPG case '25504446': // PDF isValid = true; break; default: isValid = false; break; } observer.next(isValid ? null : { invalidMimeType: true }); observer.complete(); }); fileReader.readAsArrayBuffer(file); }); };
原问题中的关键代码参考
组件处理函数:
onDocPicked(event: Event, type: string) { const file = (event.target as HTMLInputElement).files[0]; this.customerForm.get('docs').patchValue({ [type]: file }); this.customerForm.get('docs').get(type).updateValueAndValidity(); this.customerForm.get('docs').get(type).markAsDirty(); console.log(this.customerForm.get('docs').get(type)); // 上传合法文件时展开对象显示valid为true console.log(this.customerForm.get('docs').get(type).valid); // 始终返回false }
内容的提问来源于stack exchange,提问作者Martin Carre
相关产品推荐
相关产品推荐

