错误TypeError:无法在'FormData'上执行'append':参数2不是'Blob'类型。角度6

时间:2019-07-13 03:55:46

标签: angular forms express angular-reactive-forms multer

PostCreateComponent.html:56错误TypeError:对'FormData'执行'append'失败:参数2不是'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)

不知道为什么会发生错误真令人沮丧,因为在MDN上,它清楚地指出,当您添加表单数据时,可选输入是文件名,

来源:MDN

此方法有两个版本:两个和三个参数版本:

formData.append(name,value); formData.append(名称,值,文件名); ParametersSection名称。数据包含在值中的字段的名称。字段的值。这可以是USVString或Blob(包括子类,例如File)。当将Blob或File作为第二个参数传递时,报告给服务器的文件名(USVString)。 Blob对象的默认文件名是“ blob”。 File对象的默认文件名是文件的文件名。

请参阅下面的代码,重点关注posts.service.ts第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>

2 个答案:

答案 0 :(得分:1)

问题似乎是上传的文件未转换为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 });
    };
  }

来源:Convert data file to blob

答案 1 :(得分:0)

也许与您的问题完全无关,但是如果它可能对其他人有帮助,在尝试使用ant design上传多个文件时,我会使用 React 遇到相同的问题。我的问题是我正在使用这个:

const formData = new FormData()

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

将文件[0]作为对象

代替此:

const formData = new FormData()

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

哪个给我一个文件。