如何使用角度5和节点js上传图像?

时间:2018-03-21 06:09:51

标签: node.js angular

如何使用角度5和节点js上传图像?

2 个答案:

答案 0 :(得分:0)

根据您的目的,您可以在互联网上获得更多信息。

您的问题是基于上传图片,图片是文件,因此您应该调查如何上传带有节点js和Angular 5搜索互联网的文件。

  

例如,在Node JS的情况下,请查看此代码

var http = require('http');
var formidable = require('formidable');

http.createServer(function (req, res) {
  if (req.url == '/fileupload') {
    var form = new formidable.IncomingForm();
    form.parse(req, function (err, fields, files) {
      res.write('File uploaded');
      res.end();
    });
  } else {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write('<form action="fileupload" method="post" enctype="multipart/form-data">');
    res.write('<input type="file" name="filetoupload"><br>');
    res.write('<input type="submit">');
    res.write('</form>');
    return res.end();
  }
}).listen(8080);

来自https://www.w3schools.com/nodejs/nodejs_uploadfiles.asp

  

和Angular 5

<div class="form-group">
    <label for="file">Choose File</label>
    <input type="file"
           id="file"
           (change)="handleFileInput($event.target.files)">
</div>

来自Angular-5 File Upload

答案 1 :(得分:0)

如何使用ANGULAR 5/6和NODEJS上传图像文件

很长一段时间以来,我都曾遇到过同样的问题,但从未找到可以帮助我的完整答案。因此,经过大量研究,我最终得到了一些扎实的东西,并决定分享。这是对我构建的内容的简化,但是却非常详细的示例使用 Item 模型,组件,服务,路由和控制器来使用最新版本的Angular和NodeJS选择,上传和存储图片(当前Angular 6和NodeJS 8.11),但也可以在以前的版本中使用。

您会注意到我使用的是反应式表格。请不要犹豫,问自己是否不了解某些内容。我很乐意解释。我们去...

item.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, FormArray, Validators } from '@angular/forms';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Subscription } from 'rxjs';
import { mimeType } from './mime-type.validator';

import { ItemService} from '../item.service';

import { Item } from '../item.model';

@Component({
  selector: 'app-item',
  templateUrl: './item.component.html',
  styleUrls: ['./item.component.css']
})
export class ItemComponent implements OnInit {
  item: Item;

  form: FormGroup;

  imagePreview: string;
  private id: string;

  loading = false;

  constructor(public itemService: ItemService, public route: ActivatedRoute) { }

  initForm() {
    this.imagePreview = item.imagePath;

    const item = this.item ? this.item : new Item();

    return new FormGroup({
      id: new FormControl({value: item.id, disabled: true}),
      name: new FormControl(item.name, { validators: [Validators.required, Validators.minLength(3)] }),
      image: new FormControl(item.imagePath, { validators: [Validators.required], asyncValidators: [mimeType] })
    });
  }

  ngOnInit() {
    this.form = this.initForm();

    this.route.paramMap.subscribe((paramMap: ParamMap) => {
      if (paramMap.has('id')) {
        this.id = paramMap.get('id');
        this.loading = true;

        this.itemService.getItem(this.id).subscribe(data => {
          this.item = new Item(
             data._id,
             data.name ? data.name : '',
             data.imagePath ? data.imagePath : '',
          );

          this.form = this.initForm();

          this.loading = false;
        });
      } else {
        this.id = null;
        this.item = this.form.value;
      }
    });
  }

  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 = reader.result;
    };
    reader.readAsDataURL(file);
  }

  onSave() {
    if (this.form.invalid) {
      return;
    }
    this.loading = true;
    if (!this.id) { // creating item...
      const item: Item = {
        id: null,
        name: this.form.value.name,
        imagePath: null
      };
      this.itemService.createItem(item, this.form.value.image);

    } else {  // updating item...
      const item: Item = {
        id: this.id,
        name: this.form.value.name,
        imagePath: null
      };
      this.itemService.updateItem(item, this.form.value.image);
    }

    this.form.reset();
  }

}

mimeType是一个验证器,用于限制用户仅从一组图像类型中选择/加载图像文件...

mimetype.validator

import { AbstractControl } from '@angular/forms';
import { Observable, Observer, of } from 'rxjs';

export const mimeType = (control: AbstractControl): Promise<{[ key: string ]: any}> | Observable<{[ key: string ]: any}> => {
  if (typeof(control.value) === 'string') {
    return of(null);
  }
  const file = control.value as File;
  const fileReader = new FileReader();
  const frObs = Observable.create((observer: Observer<{[ key: string ]: any}>) => {
    fileReader.addEventListener('loadend', () => {
      const arr = new Uint8Array(fileReader.result).subarray(0, 4);
      let header = '';
      let isValid = false;
      for (let i = 0; i < arr.length; i++) {
        header += arr[i].toString(16);
      }
      switch (header) {
        case '89504e47':
          isValid = true;
          break;
        case '89504e47':    // png
        case '47494638':    // gif
        case 'ffd8ffe0':    // JPEG IMAGE (Extensions: JFIF, JPE, JPEG, JPG)
        case 'ffd8ffe1':    // jpg: Digital camera JPG using Exchangeable Image File Format (EXIF)
        case 'ffd8ffe2':    // jpg: CANNON EOS JPEG FILE
        case 'ffd8ffe3':    // jpg: SAMSUNG D500 JPEG FILE
        case 'ffd8ffe8':    // jpg: Still Picture Interchange File Format (SPIFF)
          isValid = true;
          break;
        default:
          isValid = false;
          break;
      }
      if (isValid) {
        observer.next(null);
      } else {
        observer.next({ invalidMimeType: true });
      }
      observer.complete();
    });
    fileReader.readAsArrayBuffer(file);
  });
  return frObs;
};

item.component.html

<form [formGroup]="form" (submit)="onSave()" *ngIf="!loading">
  <input type="text" formControlName="name" placeholder="Name" autofocus>
  <span class="error" *ngIf="form.get('name').invalid">Name is required.</span>

  <!-- IMAGE BLOCK -->
  <div class="image">
    <button class="pick-image" type="button" (click)="filePicker.click()">
      Pick Image
    </button>
    <input type="file" #filePicker (change)="onImagePicked($event)">
    
    <div class="image-preview" *ngIf="imagePreview !== '' && imagePreview && form.get('image').valid">
      <img [src]="imagePreview" [alt]="form.value.title">
    </div>
  </div>

  <div id="buttons-bar">
    <button id="submit" type="submit">SAVE</button>
  </div>
</form>

使用一些CSS隐藏输入类型“文件”的HTML元素,因为它看起来很丑陋,但仍需要触发用户浏览器打开对话框窗口以选择要上传的文件(显示一个漂亮的按钮代替,以吸引更好的用户经验)...

item.component.css

.pick-image {
  padding: 10px;
  background-color: rgba(150, 220, 255, 0.7);
  width: 100px;
}
.pick-image:hover {
  cursor: pointer;
  background-color: rgba(150, 220, 255, 0.9);
}

input[type="file"] {
  visibility: hidden;
  display: none;
}

.image-preview {
  height: 200px;
  margin: 0;
  padding: 0;
}
.image-preview img {
  height: 100%;
  width: 100%;
  object-fit: contain;
}

item.service.ts

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

import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';

import { environment } from '../../environments/environment';
import { Item } from './item.model';

const SERVER_URL = environment.API_URL + '/items/';

@Injectable({ providedIn: 'root' })
export class ItemService {
  private items: Item[] = [];
  private itemsUpdated = new Subject<{items: Item[], count: number}>();

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

  getItems(perPage: number, currentPage: number) {
    const queryParams = `?ps=${perPage}&pg=${currentPage}`;

    this.http.get<{message: string, items: any, total: number}>(SERVER_URL + queryParams)
      .pipe(map((itemData) => {
        const items = [];
        for (let i = 0; i < itemData.items.length; i++) {
          items.push(new Item(
            itemData.items[i]._id
            itemData.items[i].name,
            itemData.items[i].imagePath
          ));
        }
        return {
          items: items,
          count: itemData.total
        };
      }))
      .subscribe((mappedData) => {
        if (mappedData !== undefined) {
          this.items = mappedData.items;
          this.itemsUpdated.next({
            items: [...this.items],
            count: mappedData.count
          });
        }
      }, error => {
        this.itemsUpdated.next({items: [], count: 0});
      });
  }

  getItemUpdatedListener() {
    return this.ItemsUpdated.asObservable();
  }

  getItem(id: string) {
     return this.http.get<{
        _id: string,
        name: string,
        imagePath: string
     }>(SERVER_URL + id);
  }

  createItem(itemToCreate: Item, image: File) {
    const itemData = new FormData();
    itemData.append('name', itemToCreate.name);
    itemData.append('image', image, itemToCreate.name);

    this.http.post<{ message: string, item: Item}>(SERVER_URL, itemData ).subscribe((response) => {
      this.router.navigate(['/']);
    });
  }

  updateItem(itemToUpdate: Item, image: File | string) {
    let itemData: Item | FormData;
    if (typeof(image) === 'object') {
      itemData = new FormData();
      itemData.append('id', itemToUpdate.id);
      itemData.append('name', itemToUpdate.name);
      itemData.append('image', image, itemToUpdate.name);
    } else {
      itemData = {
        id: itemToUpdate.id,
        name: itemToUpdate.name,
        imagePath: image
      };
    }

    this.http.put(SERVER_URL + itemToUpdate.id, itemData).subscribe(
      (response) => {
        this.router.navigate(['/']);
      }
    );
  }

  deleteItem(itemId) {
    return this.http.delete<{ message: string }>(SERVER_URL + itemId);
  }

}

现在,在后端(使用ExpressJS的NodeJS)中,您可以看到您的app.js,其中,您可以按以下顺序引用与数据库(此处使用MongoDB)和中间件的连接...

app.js

const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');

const itemRoutes = require('./routes/items');

const app = express();

mongoose.connect(
    'mongodb+srv://username:' +
    process.env.MONGO_ATLAS_PW +
    '@cluster0-tmykc.mongodb.net/database-name', { useNewUrlParser: true })
  .then(() => {
    console.log('Mongoose is connected.');
  })
  .catch(() => {
    console.log('Connection failed!');
  });

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/', express.static(path.join(__dirname, 'angular')));

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS');
  next();
});

app.use('/api/items', itemRoutes);

app.use((req, res, next) => {
  res.sendFile(path.join(__dirname, 'angular', 'index.html'));
});

module.exports = app;

您的itemRoutes items.js文件位于routes文件夹中...

routes / items.js

const express = require('express');
const extractFile = require('./../middleware/file');
const router = express.Router();

const ItemController = require('./../controllers/items');

router.get('', ItemController.get_all);

router.get('/:id', ItemController.get_one);

router.post('', extractFile, ItemController.create);

router.put('/:id', extractFile, ItemController.update);

router.delete('/:id', ItemController.delete);

module.exports = router;

您的items.js文件夹中的ItemController controllers文件...

controllers / items.js

const Item = require('./../models/item');

// ...

exports.create = (req, res, next) => {
  const url = req.protocol + '://' + req.get('host');

  const item = req.body; // item = item to create

  const itemToCreate = new Item({
    name:         item.name,
    imagePath:    url + "/images/" + req.file.filename,
  });

  itemToCreate.save().then((newItem) => {
    res.status(201).json({
      message: 'Item created.',
      item: {
        ...newItem,
        id: newItem._id
      }
    });
  })
  .catch(error => {
    console.log('Error while creating item: ', error);
    res.status(500).json({
      message: 'Creating item failed!',
      error: error
    });
  });
};

// ...

最后,是 file.js 中间件:

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) => {
    const isValid = MIME_TYPE_MAP[file.mimetype];
    let error = isValid ? null : new Error('Invalid mime type');
    cb(error, 'images');
  },
  filename: (req, file, cb) => {
    const name = file.originalname.toLowerCase().split(' ').join('-');

    const ext = MIME_TYPE_MAP[file.mimetype];
    cb(null, name + '-' + Date.now() + '.' + ext);
  }
});

module.exports = multer({storage: storage}).single('image');

我希望这会有所帮助。