Angular模板驱动表单调用setValue报错:无表单控件已注册
解决Angular模板驱动表单setValue报错:There are no form controls registered with this group yet
这个报错的核心原因很明确:在你调用setValue的时候,模板里的表单控件还没完成和NgForm的注册关联。Angular的生命周期中,ngOnInit阶段模板还处于渲染初期,表单控件还没被添加到表单组里,直接调用setValue自然会找不到对应的控件。
下面给你几种可行的解决方案,按推荐程度排序:
方案1:使用ngModel双向绑定(最符合模板驱动表单的设计)
模板驱动表单的优势就是通过模板声明来管理表单状态,直接用双向绑定可以避免手动操作表单对象的麻烦:
步骤1:在组件类中定义表单绑定对象
// 先定义一个用于表单绑定的device对象 device: DeviceModel = { name: '', type: '', platform: '', udid: '' }; // 保留你的其他代码... ngOnInit() { this.route.params.subscribe((params: Params) => { this.id = +params['id']; this.editMode = params['id'] != null; if (this.editMode) { this.editedDevice = this.deviceService.getDevice(params['id']); // 直接把编辑设备的数据赋值给绑定对象(注意udid对应frame字段) this.device = { ...this.editedDevice, udid: this.editedDevice.frame }; } }); }
步骤2:修改模板中的input为双向绑定
把每个input的ngModel改成双向绑定语法[(ngModel)]:
<input type="text" id="name" class="form-control" name="name" [(ngModel)]="device.name" required> <input type="text" id="type" class="form-control" name="type" [(ngModel)]="device.type" required> <input type="text" id="platform" class="form-control" name="platform" [(ngModel)]="device.platform" required> <input type="text" id="udid" class="form-control" name="udid" [(ngModel)]="device.udid" required>
这样Angular会自动同步数据到表单控件,完全不需要手动调用setValue,也不会有生命周期的冲突问题。
方案2:用setTimeout延迟执行setValue(快速修复)
如果你不想修改模板,只想快速修复现有代码,可以把setValue的调用放到setTimeout里,让Angular完成下一次变更检测后再执行,此时表单控件已经注册完成:
if (this.editMode) { this.editedItemIndex = params['id']; this.editedDevice = this.deviceService.getDevice(params['id']); console.log(this.editedDevice.frame); // 延迟到下一个tick执行setValue setTimeout(() => { this.deviceForm.setValue({ name: this.editedDevice.name, platform: this.editedDevice.platform, type: this.editedDevice.type, udid: this.editedDevice.frame }); }); }
这里的setTimeout不需要指定时间,默认0毫秒就会让代码在Angular的变更检测周期之后执行。
方案3:改用ngAfterViewInit钩子(需注意异步逻辑)
ngAfterViewInit是在视图完全初始化后触发的,此时表单控件已经注册完成,但要注意路由参数的订阅是异步的,可能在这个钩子之后才拿到数据,所以需要结合订阅来处理:
// 记得导入Subscription import { Subscription } from 'rxjs'; private paramsSub: Subscription; ngAfterViewInit() { this.paramsSub = this.route.params.subscribe((params: Params) => { this.id = +params['id']; this.editMode = params['id'] != null; if (this.editMode) { this.editedDevice = this.deviceService.getDevice(params['id']); this.deviceForm.setValue({ name: this.editedDevice.name, platform: this.editedDevice.platform, type: this.editedDevice.type, udid: this.editedDevice.frame }); } }); } ngOnDestroy() { // 取消订阅避免内存泄漏 if (this.paramsSub) { this.paramsSub.unsubscribe(); } }
这种方式要注意,如果组件多次复用,必须在ngOnDestroy里取消订阅,避免内存泄漏问题。
内容的提问来源于stack exchange,提问作者Sajid Manzoor
相关产品推荐
相关产品推荐

