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>