javascriptangulartypescriptexpress

Angular 17 does not post and i get an TypeError in dev console


I'm following a tutorial for image upload in Angular, but I get this error in my Chrome dev console whenever I post the form

TypeError: Argument 2 ('blobValue') to FormData.append must be an instance of Blob

here is my code for the routing to the database(Mongo Atlas):

const express = require("express");
const router = express.Router();
const Post = require("../models/post");
const multer = require("multer");

const MIME_TYPE_MAP = {
  "image/png": "png",
  "image/jpeg": "jpg",
  "image/jpg": "jpg",
};

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "Api/upload");
  },
  filename: (req, file, cb) => {
    const name = file.originalname.toLowerCase().split(" ").join("-");
    const extension = MIME_TYPE_MAP[file.mimetype];
    cb(null, name + "-" + Date.now() + "." + extension);
  },
});

/* Here i post to the Mongo database  */
router.post(
  "",
  multer({ storage: storage }).single("image"),
  (req, res, next) => {
    const post = new Post({
      title: req.body.title,
      content: req.body.content,
    });
    post.save().then((createdPost) => {
      //console.log(result);
      res.status(201).json({
        message: "Post added succesfully to MongoDB!",
        postId: createdPost._id,
      });
    });
    //console.log(post);
  }
);

/* Here i fetch from the Mongo database  */
router.get("", (req, res, next) => {
  Post.find().then((documents) => {
    res
      .status(200)
      .json({ message: "Post fetched Successfully!", posts: documents });
  });
});

/* Here i delete from the Mongo database */
router.delete("/:id", (req, res, next) => {
  /* console.log(req.params.id);
  res.status(200).json({ message: "Post has een deleted!" }); */
  Post.deleteOne({ _id: req.params.id }).then((result) => {
    console.log(result);
    res.status(200).json({ message: "Post has een deleted!" });
  });
});

router.put("/:id", (req, res, next) => {
  const post = new Post({
    _id: req.body.id,
    title: req.body.title,
    content: req.body.content,
  });
  Post.updateOne({ _id: req.params.id }, post).then((result) => {
    // console.log(result);
    res.status(200).json({ message: "Update Post Successful!" });
  });
});

router.get("/:id", (req, res, next) => {
  Post.findById(req.params.id).then((post) => {
    if (post) {
      res.status(200).json(post);
    } else {
      res.status(404).json({ message: "Post not Found!" });
    }
  });
});

module.exports = router;

and this is the code for the post-service

import { Injectable } from '@angular/core';
import { Post } from './postModel';
import { Subject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class PostServiceService {
  private posts: Post[] = [];
  private postsUpdated = new Subject<Post[]>();

  constructor(private http: HttpClient) {}

  /*  getPost(): Observable<Post[]> {
    const posts = of(this.posts);
    return posts;
  } */

  /* Here I get posts route */
  getPost() {
    //return [...this.posts];
    this.http
      .get<{ message: string; posts: any }>('http://localhost:4000/api/posts')
      .pipe(
        map((postData) => {
          return postData.posts.map((post: any) => {
            return {
              title: post.title,
              content: post.content,
              id: post._id,
            };
          });
        })
      )
      .subscribe((transformedtData) => {
        this.posts = transformedtData;
        this.postsUpdated.next([...this.posts]);
      });
  }

  getPostUpdatedListener() {
    return this.postsUpdated.asObservable();
  }

  getPosts(id: any) {
    return this.http.get<{ _id: string; title: string; content: string }>(
      'http://localhost:4000/api/posts/' + id
    );
  }

  addPost(title: string, content: string, image: File) {
    /*     const post: Post = {
      id: id,
      title: title,
      content: content,
    }; */
    const postData = new FormData();
    postData.append('title', title);
    postData.append('content', content);
    postData.append('image', image, title);

    this.http
      .post<{ message: string; postId: string }>(
        'http://localhost:4000/api/posts',
        postData
      )
      .subscribe((responseData) => {
        const post: Post = {
          id: responseData.postId,
          title: title,
          content: content,
        };
        /*   const id = responseData.postId;
        post.id = id; */
        //console.log(responseData.message);
        this.posts.push(post);
        this.postsUpdated.next([...this.posts]);
      });
  }

  updatePost(id: string, title: string, content: string) {
    const post: Post = { id: id, title: title, content: content };
    this.http
      .put('http://localhost:4000/api/posts/' + id, post)
      .subscribe((response) => /*console.log(response)*/ {
        const updatedPosts = [...this.posts];
        const oldPostsIndex = updatedPosts.findIndex((p) => p.id === post.id);
        updatedPosts[oldPostsIndex] = post;
        this.posts = updatedPosts;
        this.postsUpdated.next([...this.posts]);
      });
  }

  deletePost(postId: string) {
    this.http
      .delete('http://localhost:4000/api/posts/' + postId)
      .subscribe(() => {
        const updatedPosts = this.posts.filter((post) => post.id !== postId);
        this.posts = updatedPosts;
        this.postsUpdated.next([...this.posts]);
      });
  }
}```

Solution

  • The error you encountered shows there’s a problem with the way the image file is being handled in the FormData. I made few changes to the code as shown below:

    import { Injectable } from '@angular/core';
    import { Post } from './postModel';
    import { Subject } from 'rxjs';
    import { HttpClient } from '@angular/common/http';
    import { map } from 'rxjs/operators';
    
    @Injectable({
      providedIn: 'root',
    })
    export class PostServiceService {
      private posts: Post[] = [];
      private postsUpdated = new Subject<Post[]>();
    
      constructor(private http: HttpClient) {}
    
      getPost() {
        this.http
          .get<{ message: string; posts: any }>('http://localhost:4000/api/posts')
          .pipe(
            map((postData) => {
              return postData.posts.map((post: any) => {
                return {
                  title: post.title,
                  content: post.content,
                  id: post._id,
                  imagePath: post.imagePath // Add this to store image path
                };
              });
            })
          )
          .subscribe((transformedData) => {
            this.posts = transformedData;
            this.postsUpdated.next([...this.posts]);
          });
      }
    
      getPostUpdatedListener() {
        return this.postsUpdated.asObservable();
      }
    
      getPosts(id: string) {
        return this.http.get<{ _id: string; title: string; content: string; imagePath: string }>(
          'http://localhost:4000/api/posts/' + id
        );
      }
    
      addPost(title: string, content: string, image: File) {
        const postData = new FormData();
        postData.append('title', title);
        postData.append('content', content);
        postData.append('image', image, image.name); // Use image.name instead of title
    
        this.http
          .post<{ message: string; post: { id: string; title: string; content: string; imagePath: string } }>(
            'http://localhost:4000/api/posts',
            postData
          )
          .subscribe((responseData) => {
            const post: Post = {
              id: responseData.post.id,
              title: title,
              content: content,
              imagePath: responseData.post.imagePath
            };
            this.posts.push(post);
            this.postsUpdated.next([...this.posts]);
          });
      }
    
      updatePost(id: string, title: string, content: string, image: File | string) {
        let postData: FormData | Post;
        if (typeof image === 'object') {
          // If updating with new file
          postData = new FormData();
          postData.append('id', id);
          postData.append('title', title);
          postData.append('content', content);
          postData.append('image', image, image.name);
        } else {
          // If updating without changing file
          postData = {
            id: id,
            title: title,
            content: content,
            imagePath: image
          };
        }
    
        this.http
          .put('http://localhost:4000/api/posts/' + id, postData)
          .subscribe(response => {
            const updatedPosts = [...this.posts];
            const oldPostIndex = updatedPosts.findIndex(p => p.id === id);
            const post: Post = {
              id: id,
              title: title,
              content: content,
              imagePath: typeof image === 'string' ? image : ''
            };
            updatedPosts[oldPostIndex] = post;
            this.posts = updatedPosts;
            this.postsUpdated.next([...this.posts]);
          });
      }
    
      deletePost(postId: string) {
        this.http
          .delete('http://localhost:4000/api/posts/' + postId)
          .subscribe(() => {
            const updatedPosts = this.posts.filter(post => post.id !== postId);
            this.posts = updatedPosts;
            this.postsUpdated.next([...this.posts]);
          });
      }
    }
    

    Changes I’ve made are:

    You need to also update the backend route to handle the image properly:

    router.post(
      "",
      multer({ storage: storage }).single("image"),
      (req, res, next) => {
        const url = req.protocol + '://' + req.get("host");
        const post = new Post({
          title: req.body.title,
          content: req.body.content,
          imagePath: url + "/images/" + req.file.filename
        });
        post.save().then(createdPost => {
          res.status(201).json({
            message: "Post added successfully",
            post: {
              id: createdPost._id,
              title: createdPost.title,
              content: createdPost.content,
              imagePath: createdPost.imagePath
            }
          });
        });
      }
    );
    

    Your Post mongoose model should include the imagePath field:

    const postSchema = mongoose.Schema({
      title: { type: String, required: true },
      content: { type: String, required: true },
      imagePath: { type: String, required: true }
    });
    

    These changes should resolve the TypeError and image upload issue. Try them and see.