Angular提交新产品报400无图片错误,Postman正常,求排查
问题:Angular提交产品时提示"No image in the request",但Postman调用正常
在本地环境通过Angular应用提交新产品时,收到400错误提示“No image in the request”,但使用Postman调用相同的Node.js Post API却能正常创建产品。以下是相关代码及问题分析和解决方案:
后端Post API代码
router.post(`/`, uploadOptions.single('image'), async (req, res) =>{ const category = await Category.findById(req.body.category); if(!category) return res.status(400).send('Invalid Category') const file = req.file; if(!file) return res.status(400).send('No image in the request') const fileName = req.file.filename const basePath = `${req.protocol}://${req.get('host')}/public/uploads/`; let product = new Product({ name: req.body.name, description: req.body.description, richDescription: req.body.richDescription, image: `${basePath}${fileName}`, brand: req.body.brand, price: req.body.price, category: req.body.category, countInStock: req.body.countInStock, rating: req.body.rating, numReviews: req.body.numReviews, isFeatured: req.body.isFeatured, }) product = await product.save(); if(!product) return res.status(500).send('The product cannont be created') res.send(product); })
Angular表单代码
<div class="container"> <div class="fadein"> <h2 class="page-header">add new product </h2> <!-- <div class="alert alert-danger" *ngIf="" role="alert"></div> --> <form #addProductForm="ngForm" (ngSubmit)="SubmitData(addProductForm)"> <div class="form-group"> <label for="name">Name <sup class="red">*</sup> </label> <input required name="name" id="name" #name="ngModel" ngModel type="text" class="form-control"> </div> <div class="form-group"> <label for="description">description <sup class="red">*</sup> </label> <input required name="description" id="description" #description="ngModel" ngModel type="text" class="form-control"> </div> <div class="form-group"> <label for="richDescription">richDescription <sup class="red">*</sup> </label> <input required name="richDescription" id="richDescription" #richDescription="ngModel" ngModel type="text" class="form-control"> </div> <div class="form-group"> <label for="brand">brand <sup class="red">*</sup> </label> <input required name="brand" id="brand" #brand="ngModel" ngModel type="text" class="form-control"> </div> <div class="form-group"> <label for="price">price <sup class="red">*</sup> </label> <input required name="price" id="price" #price="ngModel" ngModel type="number" class="form-control"> </div> <div class="form-group"> <label for="category">category <sup class="red">*</sup> </label> <input required name="category" id="category" #category="ngModel" ngModel type="text" class="form-control"> </div> <div class="form-group"> <label for="countInStock">countInStock <sup class="red">*</sup> </label> <input required name="countInStock" id="countInStock" #countInStock="ngModel" ngModel type="number" class="form-control"> </div> <div class="form-group"> <label for="image">image <sup class="red">*</sup> </label> <input required type="file" name="file" #file="ngModel" [ngModel] accept="image/*"> </div> <br> <input type="submit" class="btn btn-primary" value="Add Product" [disabled]="!addProductForm.valid"> </form> </div> </div>
Angular组件提交代码
SubmitData(form: { value: { category: string; name: string; description: string; brand: string; richDescription: string; price: number; countInStock: number; image: File; }; }) const data = { name: form.value.name, description: form.value.description, richDescription: form.value.richDescription, brand: form.value.brand, price: form.value.price, category: form.value.category, countInStock: form.value.countInStock, image: form.value.image, }; this.productService.postData(data).subscribe(response => { console.log(response); }); }
Angular产品服务代码
import { HttpClient, HttpHeaders , HttpEvent} from "@angular/common/http"; import { Injectable } from '@angular/core'; import { Product } from "../models/Product"; import { map, Observable, switchMap, tap } from "rxjs"; @Injectable({ providedIn: 'root' }) export class ProductService { // jsonURL = 'assets/data.json'; warehouseURL = 'http://localhost:5000/v1/products/'; constructor(private http: HttpClient) { } getProducts(): Observable<Product[]> { return this.http.get<Product[]>(this.warehouseURL) .pipe( tap(data => console.log('All: ', JSON.stringify(data))), ); } getProduct(_id: string): Observable<Product | any> { return this.getProducts() .pipe( map((products: Product[]) => products.find(p => p._id === _id) ) ) } RemoveProduct(_id: string){ this.http.delete<Product[]>(this.warehouseURL+ _id) .subscribe(()=>{ }) } postData(data: any): Observable<any>{ return this.http.post(`${this.warehouseURL}`, data) .pipe( tap(data => console.log('All: ', JSON.stringify(data))), ); } }
问题根源分析
- 字段名不匹配:后端使用
uploadOptions.single('image'),说明期望接收的文件字段名为image,但Angular表单中的文件输入name属性是file,导致后端无法识别上传的文件。 - 请求格式错误:文件上传需要使用
multipart/form-data格式的请求体,而当前Angular代码是直接将数据作为JSON发送,无法正确传输二进制文件数据(Postman中你应该使用了form-data格式,所以能成功)。 - 文件绑定无效:ngModel并不支持文件输入控件的双向绑定,无法正确获取到选中的文件对象。
解决方案
1. 修改Angular表单的文件输入字段
将文件输入的name改为image,并移除无效的ngModel绑定:
<div class="form-group"> <label for="image">image <sup class="red">*</sup> </label> <input required type="file" name="image" #imageInput accept="image/*"> </div>
2. 修改组件提交逻辑,使用FormData构建请求
在组件中通过@ViewChild获取文件输入元素,然后用FormData封装所有表单数据:
import { ViewChild, ElementRef } from '@angular/core'; // 组件类内添加 @ViewChild('imageInput') imageInput!: ElementRef<HTMLInputElement>; SubmitData(form: any) { const formData = new FormData(); // 添加文本字段 formData.append('name', form.value.name); formData.append('description', form.value.description); formData.append('richDescription', form.value.richDescription); formData.append('brand', form.value.brand); formData.append('price', form.value.price.toString()); formData.append('category', form.value.category); formData.append('countInStock', form.value.countInStock.toString()); // 添加文件 const file = this.imageInput.nativeElement.files?.[0]; if (file) { formData.append('image', file); } this.productService.postData(formData).subscribe(response => { console.log(response); }); }
3. 调整产品服务的请求方法
不需要手动设置请求头,浏览器会自动为FormData添加正确的multipart/form-data头:
postData(data: FormData): Observable<any>{ return this.http.post(`${this.warehouseURL}`, data) .pipe( tap(data => console.log('All: ', JSON.stringify(data))), ); }
内容的提问来源于stack exchange,提问作者Nour Fares
相关产品推荐
相关产品推荐

