ERROR TypeError: Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'. Angular 6
Asked Answered
B

3

7

PostCreateComponent.html:56 ERROR TypeError: Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'.

at PostsService.push../src/app/posts/posts.service.ts.PostsService.addPost (posts.service.ts:99)

at PostCreateComponent.push../src/app/posts/post-create/post-create.component.ts.PostCreateComponent.onSavePost (post-create.component.ts:165)

it is so frustrating not knowing why the error is happening because on MDN, it clearly state, when you append a form data, the optional input is the file name,

Source: MDN

There are two versions of this method: a two and a three parameter version:

formData.append(name, value); formData.append(name, value, filename); ParametersSection name. The name of the field whose data is contained in the value.The field's value. This can be a USVString or Blob (including subclasses such as File). The filename reported to the server (a USVString), when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file's filename.

Please see code below focus on posts.service.ts line 99

post.create.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Subscription } from 'rxjs';
import { PostsService } from '../posts.service';
import { Post } from '../post.model';
import { mimeType } from './mime-type.validator';
import { AuthService } from 'src/app/auth/auth.service';

export interface CATEGORY {
  value: string;
  viewValue: string;
}

export interface DURATION {
  value: string;
  viewValue: string;
}

@Component({
  selector: 'app-post-create',
  templateUrl: './post-create.component.html',
  styleUrls: ['./post-create.component.css']
})
export class PostCreateComponent implements OnInit, OnDestroy {
  types: CATEGORY[] = [
    {value: 'Feature Film', viewValue: 'Feature Film'},
    {value: 'Short Film', viewValue: 'Short Film'},
    {value: 'TV Series', viewValue: 'TV Series'}
  ];

  contracts: DURATION[] = [
    {value: '3 Month', viewValue: '3 Month'},
    {value: '6 Month', viewValue: '6 Month'},
    {value: '9 Month', viewValue: '9 Month'},
    {value: '12 Month', viewValue: '12 Month'},
  ];

  enteredName = '';
  enteredContent = '';
  enteredGenre = '';
  enteredAuthor = '';
  enteredDuration = '';
  enteredYear = '';
  enteredCategory = '';
  enteredContractDuration = '';
  post: Post;
  isLoading = false;
  isLinear = false;
  form: FormGroup;
  imagePreview: string;
  private mode = 'create';
  private postId: string;
  private authStatusSub: Subscription;

  constructor(
    public postsService: PostsService,
    public route: ActivatedRoute,
    private authService: AuthService
  ) {}

  ngOnInit() {
    this.authStatusSub = this.authService
    .getAuthStatusListener()
    .subscribe(authStatus => {
      this.isLoading = false;
    });
    this.form = new FormGroup({
      name: new FormControl(null, {
        validators: [Validators.required, Validators.minLength(3)]
      }),
      content: new FormControl(null, { validators: [Validators.required] }),
      image: new FormControl(null, {
        validators: [Validators.required],
        asyncValidators: [mimeType]
      }),
      genre: new FormControl(null, {
        validators: [Validators.required, Validators.minLength(3)]
      }),
      author: new FormControl(null, {
        validators: [Validators.required, Validators.minLength(3)]
      }),
      duration: new FormControl(null, {
        validators: [Validators.required, Validators.minLength(3)]
      }),
      year: new FormControl(null, {
        validators: [Validators.required, Validators.minLength(3)]
      }),
      category: new FormControl(null, {
        validators: [Validators.required, Validators.minLength(3)]
      }),
      contractDuration: new FormControl(null, {
        validators: [Validators.required, Validators.minLength(3)]
      })
    });
    this.route.paramMap.subscribe((paramMap: ParamMap) => {
      if (paramMap.has('postId')) {
        this.mode = 'edit';
        this.postId = paramMap.get('postId');
        this.isLoading = true;
        this.postsService.getPost(this.postId).subscribe(postData => {
          this.isLoading = false;
          this.post = {
            id: postData._id,
            name: postData.name,
            genre: postData.genre,
            author: postData.author,
            duration: postData.duration,
            year: postData.year,
            category: postData.category,
            content: postData.content,
            imagePath: postData.imagePath,
            creator: postData.creator,
            adminApproval: postData.adminApproval,
            isApproved: postData.isApproved,
            createdAt: postData.createdAt,
            contractDuration: postData.contractDuration
          };
          this.form.setValue({
            name: this.post.name,
            content: this.post.content,
            image: this.post.imagePath,
            genre: this.post.genre,
            author: this.post.author,
            duration: this.post.duration,
            year: this.post.year,
            category: this.post.category,
            contractDuration: this.post.contractDuration
          });
        });
      } else {
        this.mode = 'create';
        this.postId = null;
      }
    });
  }

  onImagePicked(event: Event) {
    const file = (event.target as HTMLInputElement).files[0];
    this.form.patchValue({ image: file });
    this.form.get('image').updateValueAndValidity();
    const reader = new FileReader();
    reader.onload = () => {
      this.imagePreview = <string>reader.result;
    };
    reader.readAsDataURL(file);
  }

  onSavePost() {
    if (this.form.invalid) {
      return;
    }
    this.isLoading = true;
    if (this.mode === 'create') {
      console.log(this.form.value.name,
        this.form.value.content,
        this.form.value.image,
        this.form.value.genre,
        this.form.value.author,
        this.form.value.duration,
        this.form.value.year,
        this.form.value.category,
        this.form.value.contractDuration);
      this.postsService.addPost(
        this.form.value.name,
        this.form.value.content,
        this.form.value.image,
        this.form.value.genre,
        this.form.value.author,
        this.form.value.duration,
        this.form.value.year,
        this.form.value.category,
        this.form.value.contractDuration
      );
    } else {
      this.postsService.updatePost(
        this.postId,
        this.form.value.name,
        this.form.value.content,
        this.form.value.image,
        this.form.value.genre,
        this.form.value.author,
        this.form.value.duration,
        this.form.value.year,
        this.form.value.category,
        this.form.value.contractDuration
      );
    }
    this.form.reset();
  }

  ngOnDestroy() {
   this.authStatusSub.unsubscribe();
  }
}

post.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Router } from '@angular/router';

import { Post } from './post.model';

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

  constructor(private http: HttpClient, private router: Router) {}

  getPosts(postsPerPage: number, currentPage: number) {
    const queryParams = `?pagesize=${postsPerPage}&page=${currentPage}`;
    this.http
      .get<{ message: string; posts: any; maxPosts: number }>(
        'http://localhost:3000/api/posts' + queryParams
      )
      .pipe(
        map(postData => {
          return {
            posts: postData.posts.map(post => {
              return {
                name: post.name,
                genre: post.genre,
                author: post.author,
                duration: post.duration,
                year: post.year,
                category: post.category,
                content: post.content,
                id: post._id,
                imagePath: post.imagePath,
                creator: post.creator,
                adminApproval: post.adminApproval,
                isApproved: post.isApproved,
                createdAt: post.createdAt,
                contractDuration: post.contractDuration
              };
            }),
            maxPosts: postData.maxPosts
          };
        })
      )
      .subscribe(transformedPostData => {
        console.log(transformedPostData);
        this.posts = transformedPostData.posts;
        this.postsUpdated.next({
          posts: [...this.posts],
          postCount: transformedPostData.maxPosts
        });
      });
  }

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

  getPost(id: string) {
    return this.http.get<{
      _id: string;
      name: string;
      genre: string;
      author: string;
      duration: string;
      year: string;
      category: string;
      content: string;
      imagePath: string;
      creator: string;
      adminApproval: boolean;
      isApproved: boolean;
      createdAt: Date;
      contractDuration: string;
    }>('http://localhost:3000/api/posts/' + id);
  }

  addPost(
    name: string,
    genre: string,
    author: string,
    duration: string,
    year: string,
    category: string,
    content: string,
    contractDuration: string,
    image: File) {
    const postData = new FormData();
    postData.append('name', name);
    postData.append('genre', genre);
    postData.append('author', author);
    postData.append('duration', duration);
    postData.append('year', year);
    postData.append('category', category);
    postData.append('content', content);
    postData.append('contractDuration', contractDuration);
    postData.append('image', image, name);
    this.http
      .post<{ message: string; post: Post }>(
        'http://localhost:3000/api/posts',
        postData
      )
      .subscribe(responseData => {
        this.router.navigate(['/programs']);
      });
  }

  updatePost(
    id: string,
    name: string,
    genre: string,
    author: string,
    duration: string,
    year: string,
    category: string,
    content: string,
    contractDuration: string,
    image: File | string) {
    let postData: Post | FormData;
    if (typeof image === 'object') {
      postData = new FormData();
      postData.append('id', id);
      postData.append('name', name);
      postData.append('genre', genre);
      postData.append('author', author);
      postData.append('duration', duration);
      postData.append('year', year);
      postData.append('category', category);
      postData.append('contractDuration', contractDuration);
      postData.append('content', content);
      postData.append('image', image, name);
    } else {
      postData = {
        id: id,
        name: name,
        genre: genre,
        author: author,
        duration: duration,
        year: year,
        category: category,
        contractDuration: contractDuration,
        content: content,
        imagePath: image,
        creator: null,
        adminApproval: null,
        isApproved: null,
        createdAt: null
      };
    }
    this.http
      .put('http://localhost:3000/api/posts/' + id, postData)
      .subscribe(response => {
        this.router.navigate(['/programs']);
      });
  }

  deletePost(postId: string) {
    return this.http
      .delete('http://localhost:3000/api/posts/' + postId);
  }
}

post-create.component.html

<mat-card>
  <mat-spinner *ngIf="isLoading"></mat-spinner>
  <form [formGroup]="form"  (submit)="onSavePost()" *ngIf="!isLoading">
    <label class="ui-label" for="form_name">Title</label>
    <mat-form-field>
      <input matInput type="text" formControlName="name" placeholder="Teza">
      <mat-error *ngIf="form.get('name').invalid">Please enter the films name.</mat-error>
    </mat-form-field>
    <label class="ui-label" for="form_vertical_preview">Horizontal poster</label>
      <div class="ui-caption">
        Recommended: PNG or JPG file @ 740x420 resolution.
      </div>
      <button mat-stroked-button type="button" (click)="filePicker.click()">Pick Image</button>
    <div class="image-picker">
      <input type="file" #filePicker (change)="onImagePicked($event)">
    <div class="image-preview" *ngIf="imagePreview !== '' && imagePreview && form.get('image').valid">
      <img [src]="imagePreview" [alt]="form.value.name">
    </div>
  </div>
    <label class="ui-label" for="form_description">Description</label>
    <mat-form-field>
      <textarea matInput rows="4" formControlName="content" placeholder="Set in 1970s Ethiopia, Teza (Morning Dew) tells the story of a young Ethiopian as he returns from West Germany a postgraduate. Anberber comes back to a country at the height of the Cold War and under the Marxist regime of Mengistu Haile Mariam."></textarea>
      <mat-error *ngIf="form.get('content').invalid">Please enter a description.</mat-error>
    </mat-form-field>
    <label class="ui-label" for="form_genre">Genre</label>
    <mat-form-field>
      <input matInput type="text" formControlName="genre" placeholder="Action, Adventure, Romance ....">
      <mat-error *ngIf="form.get('genre').invalid">Please enter a program genre.</mat-error>
    </mat-form-field>
    <label class="ui-label" for="form_author">Author</label>
    <mat-form-field>
      <input matInput type="text" formControlName="author" placeholder="Haile Gerima, Zeresenay ...">
      <mat-error *ngIf="form.get('author').invalid">Please enter an author.</mat-error>
    </mat-form-field>
    <label class="ui-label" for="form_duration">Duration</label>
    <mat-form-field>
      <input matInput type="text" formControlName="duration" placeholder="2h35m">
      <mat-error *ngIf="form.get('duration').invalid">Please enter a duration.</mat-error>
    </mat-form-field>
    <label class="ui-label" for="form_year">Year</label>
    <mat-form-field>
      <input matInput type="text" formControlName="year" placeholder="2019">
      <mat-error *ngIf="form.get('year').invalid">Please enter a year.</mat-error>
    </mat-form-field>
    <label class="ui-label" for="form_category">Category</label>
    <mat-form-field>
        <mat-select placeholder="shortfilm, feature film, tv series" formControlName="category">
            <mat-option *ngFor="let type of types" [value]="type.value">
              {‌{type.viewValue}}
            </mat-option>
        </mat-select>
      <input matInput type="text" formControlName="category" placeholder="">
      <mat-error *ngIf="form.get('category').invalid">Please enter a category.</mat-error>
    </mat-form-field>
    <mat-form-field>
        <mat-select placeholder="3 Months, 6 Months ..." formControlName="contractDuration">
            <mat-option *ngFor="let contract of contracts" [value]="contract.value">
              {‌{contract.viewValue}}
            </mat-option>
        </mat-select>
      <input matInput type="text" formControlName="contractDuration" placeholder="">
      <mat-error *ngIf="form.get('contractDuration').invalid">Please enter a contract Duration.</mat-error>
    </mat-form-field>
    <button mat-raised-button color="accent" type="submit">Save Post</button>
    </form>
</mat-card>

Begay answered 13/7, 2019 at 3:55 Comment(0)
A
3

The issue appears to be that the uploaded file was not converted to a Blob.

 onImagePicked(event: Event) {
    const file = (event.target as HTMLInputElement).files[0];
    this.form.patchValue({ image: file });
    this.form.get('image').updateValueAndValidity();
    const reader = new FileReader();
    reader.onload = function(e) {
      this.imagePreview = <string>reader.result;
      // convert uploaded file to blob
      const blob = new Blob([new Uint8Array(e.target.result)], {type: file.type });
    };
  }

Source: Convert data file to blob

Anaptyxis answered 6/2, 2020 at 15:54 Comment(0)
V
2

Perhaps completely unrelated to your question, but in case it may be helpful to someone else, I got the same issue using React when trying to upload multiple files using ant design. My issue was that I was using this:

const formData = new FormData()

formData.append(
    "file",
    file[0],
    file[0].name
);

Which has file[0] as an object

Instead of this:

const formData = new FormData()

formData.append(
    "file",
     file[0].originFileObj,
     file[0].originFileObj.name
);

Which gives me a file.

Valdez answered 8/11, 2020 at 6:21 Comment(0)
E
-1

In React, there is no need to convert it to blog, you can instead, in handleChange:

const file = event.target.files[0]
//console.log(file)
setFile({
    picturePreview: URL.createObjectURL(event.target.files[0]),
    pictureAsFile: event.target.files[0]
})

and call in formData like:

formData.append('file_attachment', file.pictureAsFile)

Working example here: https://youtu.be/yTGXNvHQ4BQ

Eviaevict answered 26/10, 2022 at 9:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.