Angular+Express+Mongoose开发CRUD时_id属性未定义报错排查
报错根因说明
你遇到的Cannot read property '_id' of undefined和列表能正常展示不冲突:列表渲染是Angular在*ngFor的循环作用域内正确取到了产品的名称、品牌等字段完成渲染,但事件触发环节因为变量、字段名错配,拿到的目标值本身是undefined,才会触发属性读取报错,具体错配点有三个:
- 模板循环变量不匹配:
*ngFor定义的循环变量是product,但行点击事件绑定的是不存在的变量p,触发事件时p是undefined - 字段名错配:Mongoose返回的主键字段是
_id,但模板里所有按钮传参用的是接口根本没返回的product.id、product.isbn,传过去的值都是undefined - 组件方法逻辑拧巴:
deleteProduct方法定义了两个入参,但模板调用时只传一个值,方法内部又读取不存在的第二个入参的_id字段,全程拿不到正确的产品ID
逐段修复方案
1. 修正模板的变量绑定错误
原错误代码:
<tr *ngFor="let product of products" [product] = "p" (click) = 'clicked(p)'>
改成正确写法:删掉无意义的无效绑定,事件直接传循环定义的product变量
<tr *ngFor="let product of products" (click)="clicked(product)">
2. 修正模板的字段名和按钮传参
原错误代码:
<td>{{product.isbn}}</td> <td> <button (click)="deleteProduct(product.id)" class="btn btn-danger">Delete</button> <button (click)="updateProduct(product.id)" class="btn btn-info" style="margin-left: 10px">Update</button> <button (click)="detailproduct(product.id)" class="btn btn-info" style="margin-left: 10px">Details</button> </td>
改成和接口实际返回字段匹配的写法,直接使用Mongoose返回的_id:
<td>{{product._id}}</td> <td> <button (click)="deleteProduct(product._id)" class="btn btn-danger">Delete</button> <button (click)="updateProduct(product._id)" class="btn btn-info" style="margin-left: 10px">Update</button> <button (click)="detailProduct(product._id)" class="btn btn-info" style="margin-left: 10px">Details</button> </td>
注意:原模板里调用的
detailproduct方法在组件TS代码里根本没定义,要么补全方法实现,要么替换成你实际的详情跳转逻辑。
3. 修正组件TS的方法逻辑
原错误的deleteProduct方法:
deleteProduct(_id: string, product: IProduct) { console.log('deleting product'); this.productservice.deleteProduct(product._id) .subscribe({ next:product => this.message = "product is deleted", complete: () => console.log("deleted product"), error: (mess) => this.message = mess }) }
改成入参和调用匹配的写法,直接把模板传过来的_id传给服务层,删除成功后刷新列表:
deleteProduct(_id: string) { console.log('deleting product', _id); this.productservice.deleteProduct(_id) .subscribe({ next: () => { this.message = "product is deleted"; // 删除完成后重新拉取列表,保证页面数据同步 this.loadProducts(); }, complete: () => console.log("deleted product"), error: (mess) => this.message = mess }) }
关于通过产品ID定位目标产品的说明
- Mongoose生成的
_id本身就是MongoDB文档的唯一主键,和你Express接口要求的删除、单条查询、更新接口的入参完全匹配,前端不需要额外做格式转换,直接把拿到的_id字符串传给对应接口就行。 - 如果你不想在业务代码里频繁写下划线开头的
_id字段,可以在获取列表数据后做一层简单映射,给每个产品补一个id字段等于_id的值,比如在loadProducts的订阅逻辑里改:next: (value: IProduct[]) => { // 给每个产品补id映射,后续代码直接用id即可 this.products = value.map(item => ({...item, id: item._id})) }
内容的提问来源于stack exchange,提问作者poptart
相关产品推荐
相关产品推荐

