How to save image in local folder using Angular2? - node.js

I am new to stack overflow as well as in angular2. i am currently learning how to upload image in local folder inside application using angular2. i can't understand how to save images in local folder. i have read many answers from stack overflow but all of them (almost) using angularjs not angular2. can any one please help me how to upload and save images in local folder using angular2 and nodejs. i have just html code.
<div class="form-group">
<label for="single">single</label>
<input type="file" class="form-control" name="single"
/></div>
i have spent my whole night but all in vain. can someone please give me simple tutorial by just showing how to save in local folder using angular 2

I created this very detailed example working with an Item model, component, service, route and controller to select, upload and store a picture using latest versions of Angular and NodeJS (currently Angular 6 and NodeJS 8.11) but should work in previous versions too.
I'm not going to explain much taking in consideration you can learn and understand most of it just by examining the code. But do not hesitate to ask if you don't understand something on your own. Here we go...
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();
}
}
The mimeType is a validator to limit the user selecting / loading only an image file from a group of image types...
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 class="submit" type="submit">SAVE</button>
</div>
</form>
Using some CSS to hide the input type "file" HTML Element because it looks ugly but still necessary to trigger the user browser to open a dialog window to select a file for upload (a nice button is shown instead for a better user experience)...
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) => {
return {
items: Item.extractAll(itemData.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);
}
}
Now, in the backend (NodeJS using ExpressJS), picture you have your app.js where, among other things, you reference your connection with the database (here using MongoDB) and middlewares in the following order...
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;
Your itemRoutes items.js file is in a routes folder...
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;
Your ItemController items.js file in a controllers folder...
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
});
});
};
// ...
And finally, the file.js middleware:
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, 'backend/images'); // USE THIS WHEN APP IS ON LOCALHOST
// cb(error, 'images'); // USE THIS WHEN APP IS ON A SERVER
},
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');
I simplified parts of an app of mine adapting to this example to make it easier to understand (and at same time leaving a lot in there to help completing the "circle"). I hope this helps.

Related

Upload MULTIPLE files in Angular (with Multer. Node and Express)

i have my multer uploading part:
news.js
const express = require('express');
const router = require("express").Router();
const multer = require('multer');
const News = require('../models/news');
const mongoose = require('mongoose');
const DIR = './public/'
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR)
},
filename: (req, file, cb) => {
const fileName = file.originalname.toLowerCase().split(' ').join('-')
cb(null, fileName)
},
})
//Mime Type Validation
var upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5,
},
fileFilter: (req, file, cb) => {
if (
file.mimetype == 'image/png' ||
file.mimetype == 'image/jpg' ||
file.mimetype == 'image/jpeg'
) {
cb(null, true)
} else {
cb(null, false)
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'))
}
},
})
router.post('/news', upload.array('photos', 4), (req, res, next) => {
const reqFiles = [];
const url = req.protocol + '://' + req.get('host')
for (var i = 0; i < req.files.length; i++) {
reqFiles.push(url + '/public/' + req.files[i].filename)
}
const news = new News({
_id: new mongoose.Types.ObjectId(),
title: req.body.title,
subtitle: req.body.subtitle,
text: req.body.text,
photos: reqFiles,
})
news
.save()
.then((result) => {
console.log(result)
res.status(201).json({
_id: result._id,
title: result.title,
subtitle: result.subtitle,
text: result.text,
photos: result.photos,
})
})
.catch((err) => {
console.log(err),
res.status(500).json({
error: err,
})
})
})
router.get('/', (req, res, next) => {
News.find().then((data) => {
res.status(200).json({
message: 'News retrieved successfully!',
news: data,
})
})
})
router.get('/:id', (req, res, next) => {
News.findById(req.params.id).then((data) => {
if (data) {
res.status(200).json(post)
} else {
res.status(404).json({
message: 'Not found!',
})
}
})
})
module.exports = router;
and my template with form and file input:
news-photo-uploader.component.html
<form
[formGroup]="form"
(ngSubmit)="submitForm()"
action="/news"
method="POST"
enctype="multipart/form-data">
<div class="form-group">
<input
type="file"
(change)="uploadFile($event)"
name="photos"
multiple/>
</div>
<div class="form-group">
<button type="submit">Add new post</button>
</div>
<!-- Image Preview -->
<div class="form-group">
<div class="preview" *ngIf="preview && preview !== null">
<img [src]="preview" [alt]="form.value.name" />
</div>
</div>
</form>
when i send POST request with Postman, everything works fine and i receive all (2) my image links
{
"_id": "63330b3d9b83959f4b65ba75",
"photos": [
"http://localhost:3000/public/2022-4-15_16-36-4.211.jpg",
"http://localhost:3000/public/img_20220301_210457.jpg"
]
}
but, when i try to do it with my Angular app
news.service.ts
import { News } from '../../data/interfaces/news';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { HttpHeaders, HttpClient, HttpEvent } from '#angular/common/http';
import { ApiService } from 'src/app/shared/service/api.service';
#Injectable({
providedIn: 'root',
})
export class NewsService {
path: string = '/news';
constructor(private http: HttpClient, private apiService: ApiService) {}
// Get News
getNews(): Observable<{ news: News[] }> {
return this.apiService.get(this.path);
}
// Create News
addNews(title: string, subtitle: string, text: string, newsImages: Array<File>): Observable<HttpEvent<any>> {
var formData: any = new FormData();
formData.append('title', title);
formData.append('subtitle', subtitle);
formData.append('text', text);
// formData.append('photos', newsImages);
for(let i = 0; i < newsImages.length; i++){
formData.append('photos', newsImages[i]);
}
console.log(formData);
return this.http.post<News>(`${this.apiService.url}/news`, formData, {
reportProgress: true,
observe: 'events',
});
}
}
news-photo-uploader.component.ts
import { NewsService } from './../../news.service';
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { HttpEvent, HttpEventType } from '#angular/common/http';
import { Router } from '#angular/router';
#Component({
selector: 'app-news-photo-uploader',
templateUrl: './news-photo-uploader.component.html',
styleUrls: ['./news-photo-uploader.component.scss'],
})
export class NewsPhotoUploaderComponent implements OnInit {
preview!: string;
form: FormGroup;
percentDone?: any = 0;
users: any[] = [];
constructor(public fb: FormBuilder, public router: Router, public newsService: NewsService) {
// Reactive Form
this.form = this.fb.group({
title: [''],
subtitle: [''],
text: [''],
photos: [null],
});
}
ngOnInit(): void {}
uploadFile(event: any) {
const file = (event.target as HTMLInputElement).files![0];
this.form.patchValue({
photos: file,
});
this.form.get('photos')!.updateValueAndValidity();
// File Preview
const reader = new FileReader();
reader.onload = () => {
this.preview = reader.result as string;
};
reader.readAsDataURL(file);
}
submitForm() {
this.newsService
.addNews(this.form.value.title, this.form.value.subtitle, this.form.value.text, this.form.value.photos)
.subscribe((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Sent:
console.log('Request has been made!');
break;
case HttpEventType.ResponseHeader:
console.log('Response header has been received!');
break;
}
});
}
}
then all i receive in my console or DB is empty array instead list of links
photos:[]
subtitle:""
text:""
title:""
_id:"63330f199b83959f4b65ba7b"
what should i need to do in my news.service.ts to make it fixed?

file uploading gives file name undefined in Mean stack

i'm trying to upload imae and store it in mongoDb. so far i have defined image String attribute in nodejs model. But it gives below error.
so here is my server side controller.
noticeController.js
const express = require('express');
const router = express.Router();
const multer = require('multer');
const notice = require('../models/notices');
const storage = multer.diskStorage({
destination :"./public/uploads/",
filename: (req,file,callBack) => {
callBack(null,file.fieldname+"_"+Date.now()+path.extname(file.originalname));
}
});
const upload = multer({
storage : storage
}).single('image');
/add notice
router.post('/notice/add',upload,(req,res,next)=>{
let newNotice = new notice({
title : req.body.title,
description : req.body.description,
image : req.file.filename
});
newNotice.save((err,notice)=>{
if(err){
res.json({msg : 'Failed to add notice' });
}
else{
res.json({ msg : 'Notice added Successfully'});
let socketio = req.app.get('socketio');
socketio.sockets.emit('notice.created', newNotice);
}
});
});
//update notice
router.put('/notice/update/:id',upload,(req,res,next)=>{
let newNotice = new notice({
title : req.body.title,
description : req.body.description,
image : req.file.filename
});
notice.findByIdAndUpdate({_id : req.params.id},{$set:req.body},(err,notice)=>{
if(err){
res.json({msg : 'Failed to update notice' });
}
else{
res.json({ msg : 'Notice updated Successfully'});
}
});
});
module.exports =router;
and here is the client side ts.
notice.component.ts
import {Component, Input, OnChanges, OnInit, SimpleChanges} from '#angular/core';
import {FormControl, FormGroup, Validators} from "#angular/forms";
import {MatStepper} from "#angular/material/stepper";
import {Notice} from "../../shared/models/notice";
import {NoticeService} from "../../core/services/notice.service";
import {SnackBarComponent} from "../../shared/popup-modals/snack-bar/snack-bar.component";
#Component({
selector: 'app-notice',
templateUrl: './notice.component.html',
styleUrls: ['./notice.component.css'],
providers : [NoticeService]
})
export class NoticeComponent implements OnInit, OnChanges {
#Input('objectId') selectedObjectId : string;
public imageString: String;
isEdit : boolean = false;
socket;
uploading: boolean = false;
notice: Notice = {
title: '',
description:'',
image:''
};
notices: Notice[];
finalNoticeList: Notice[] = [];
noticeForm = new FormGroup({
image: new FormControl(),
title: new FormControl('', [Validators.required]),
description: new FormControl('', [Validators.required]),
});
constructor(private noticeService: NoticeService, private customPopup : SnackBarComponent) {
}
ngOnInit(): void {
this.getNoticeList();
}
ngOnChanges(changes: SimpleChanges): void {
console.log("hello");
console.log("check "+ this.selectedObjectId);
this.getNotice(this.selectedObjectId);
this.noticeForm.disable();
}
deleteNotice(id: any) {
this.noticeService.deleteNotice(id).subscribe(data => {
this.getNoticeList();
});
}
addNotice(){
let newNotice = new Notice();
newNotice.title = this.noticeForm.controls.title.value;
newNotice.description = this.noticeForm.controls.description.value;
newNotice.image = this.noticeForm.controls.image.value;
if(this.isEdit){
this.noticeService.updateNotice(this.selectedObjectId,newNotice).subscribe(notice=>{
this.customPopup.openSnackBar("Notice Updated Successfully!","warning")
this.getNoticeList();
},error => {
console.log(error);
});
this.isEdit = false;
}
else{
this.noticeService.addNotices(newNotice).subscribe(notice=>{
this.customPopup.openSnackBar("Notice Saved Successfully!","success")
this.getNoticeList();
},error => {
console.log(error);
});
}
this.noticeForm.disable();
}
setToEdit(){
this.noticeForm.enable();
this.isEdit =true;
}
setFormData(){
console.log("title "+ this.notice.title);
this.noticeForm.controls.title.setValue(this.notice.title);
this.noticeForm.controls.description.setValue(this.notice.description);
}
getNoticeList() {
this.noticeService.getNotices()
.subscribe(notices => {
this.notices = notices;
this.finalNoticeList = notices;
});
}
getNotice(id:string){
this.noticeService.getNotice(id)
.subscribe(notice => {
this.noticeForm.controls.title.setValue(notice.title);
this.noticeForm.controls.description.setValue(notice.description);
});
}
setState(){
this.noticeForm.disable();
this.isEdit = false;
}
}
html part
<input type='file' formControlName="image" id="image" />
I'm using mutler package. I search thruogh google for an answe. But I couldn't find it. So where did I do wrong?
The provided name attribute in your html must match the string you pass to the multer call. So you should change your html to:
<input type='file' formControlName="image" name="image" id="image" />

i am trying to upload an image in MEAN stack application but when I click on submit button the page goes on for continous loading

here is my code
The screen just keeps loading whenever I click form submit button. No error is displayed however.Someone please help me with this. Thanks for the reply. I am using multer at backend and mime type validator for filtering mages of only jpeg,jpg and png images.
frontend:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from "#angular/forms";
import{AdminteacherService} from '../Services/adminteacher.service';
import { MatSpinner } from '#angular/material';
import { HttpClient } from "#angular/common/http";
import { mimeType } from "./mime-type.validator";
import { ActivatedRoute, ParamMap } from "#angular/router";
import {TeacherModel} from '../../../backend/model/teacher';
#Component({
selector: 'app-adminteacher',
templateUrl: './adminteacher.component.html',
styleUrls: ['./adminteacher.component.css']
})
export class AdminteacherComponent implements OnInit {
enteredName = "";
enteredSSN = "";
enteredBranch = "";
enteredPassword = "";
post: TeacherModel;
isLoading = false;
form: FormGroup;
imagePreview: string;
private mode = "create";
private postId: string;
constructor(public postsService: AdminteacherService,
public route: ActivatedRoute,
private http:HttpClient) { }
ngOnInit() {
this.form = new FormGroup({
name: new FormControl(null, {
validators: [Validators.required, Validators.minLength(3)]
}),
ssn: new FormControl(null, { validators: [Validators.required] }),
branch: new FormControl(null, { validators: [Validators.required] }),
password: new FormControl(null, { validators: [Validators.required] }),
image: new FormControl(null, {
validators: [Validators.required],
asyncValidators: [mimeType]
})
});
}
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 as string;
};
reader.readAsDataURL(file);
}
onSavePost() {
if (this.form.invalid) {
return;
}
this.isLoading = true;
if (this.mode === "create") {
this.postsService.addPost(
this.form.value.name,
this.form.value.image,
this.form.value.ssn,
this.form.value.branch,
this.form.value.password
);
}
postForm.value.ssn,postForm.value.password,postForm.value.branch,postForm.value.image);
this.form.reset();
}
}
SERVICE FILE:
import { HttpClient } from "#angular/common/http";
import { Subject } from "rxjs";
import { map } from "rxjs/operators";
import { Router } from "#angular/router";
import {TeacherModel} from '../../../backend/model/teacher';
#Injectable({
providedIn: 'root'
})
export class AdminteacherService {
constructor(private http:HttpClient) { }
postData:TeacherModel;
addPost(name: string,image:File,ssn: string,branch:string,password:string) {
const postData=new FormData();
postData.append("name",name);
postData.append("image",image,name);
postData.append("ssn",ssn);
postData.append("branch",branch);
postData.append("password",password);
console.log("I am here");
this.http
.post<{message: string; postId: string}>(
"http://localhost:3000/admin/addteacher",
postData
)
console.log("I am not here");
}
}
BACKEND:
const express=require("express");
const multer=require("multer");
const Post=require("../model/teacher");
const router=express.Router();
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/MyMINIPROJECT', { useNewUrlParser: true , useUnifiedTopology: true});
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 = new Error("Invalid mime type");
if (isValid) {
error = null;
}
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);
}
});
router.post('/admin/addteacher', multer({ storage: storage }).single("image"),
(req, res, next)=>{
const url = req.protocol + "://" + req.get("host");
console.log("reached backend");
const post=new Post({
name:req.body.name,
img_link: url + "/images/" + req.file.filename,
ssn:req.body.ssn,
branch:req.body.branch,
password:req.body.password
});
post.save().then(createdPost => {
res.status(201).json({
message: "Post added successfully",
post: {
...createdPost,
id: createdPost._id
}
});
});
});
module.exports = router;

Upload image file from React front end to Node/Express/Mongoose/MongoDB back end (not working)

I’ve spent most of a day looking into this and trying to make it work. This is an app with a React/Redux front end, and a Node/Express/Mongoose/MongoDB back end.
I currently have a Topics system where an authorized user can follow/unfollow topics, and an admin can Add/Remove topics.
I want to be able to upload an image file when submitting a new topic, and I want to use Cloudinary to store the image and then save the images path to the DB with the topic name.
The problem I am having is that I am unable to receive the uploaded file on the back end from the front end. I end up receiving an empty object, despite tons of research and trial/error. I haven’t finished setting up Cloudinary file upload, but I need to receive the file on the back end before even worrying about that.
SERVER SIDE
index.js:
const express = require("express");
const http = require("http");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const app = express();
const router = require("./router");
const mongoose = require("mongoose");
const cors = require("cors");
const fileUpload = require("express-fileupload");
const config = require("./config");
const multer = require("multer");
const cloudinary = require("cloudinary");
const cloudinaryStorage = require("multer-storage-cloudinary");
app.use(fileUpload());
//file storage setup
cloudinary.config({
cloud_name: "niksauce",
api_key: config.cloudinaryAPIKey,
api_secret: config.cloudinaryAPISecret
});
const storage = cloudinaryStorage({
cloudinary: cloudinary,
folder: "images",
allowedFormats: ["jpg", "png"],
transformation: [{ width: 500, height: 500, crop: "limit" }] //optional, from a demo
});
const parser = multer({ storage: storage });
//DB setup
mongoose.Promise = global.Promise;
mongoose.connect(
`mongodb://path/to/mlab`,
{ useNewUrlParser: true }
);
mongoose.connection
.once("open", () => console.log("Connected to MongoLab instance."))
.on("error", error => console.log("Error connecting to MongoLab:", error));
//App setup
app.use(morgan("combined"));
app.use(bodyParser.json({ type: "*/*" }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
router(app, parser);
//Server setup
const port = process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log("server listening on port: ", port);
TopicController/CreateTopic
exports.createTopic = function(req, res, next) {
console.log("REQUEST: ", req.body); //{ name: 'Topic with Image', image: {} }
console.log("IMAGE FILE MAYBE? ", req.file); //undefined
console.log("IMAGE FILES MAYBE? ", req.files); //undefined
const topic = new Topic(req.body);
if (req.file) {
topic.image.url = req.file.url;
topic.image.id = req.file.publid_id;
} else {
console.log("NO FILE UPLOADED");
}
topic.save().then(result => {
res.status(201).send(topic);
});
};
router.js
module.exports = function(app, parser) {
//User
app.post("/signin", requireSignin, Authentication.signin);
app.post("/signup", Authentication.signup);
//Topic
app.get("/topics", Topic.fetchTopics);
app.post("/topics/newTopic", parser.single("image"), Topic.createTopic);
app.post("/topics/removeTopic", Topic.removeTopic);
app.post("/topics/followTopic", Topic.followTopic);
app.post("/topics/unfollowTopic", Topic.unfollowTopic);
};
CLIENT SIDE
Topics.js:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Loader, Grid, Button, Icon, Form } from "semantic-ui-react";
import {
fetchTopics,
followTopic,
unfollowTopic,
createTopic,
removeTopic
} from "../actions";
import requireAuth from "./hoc/requireAuth";
import Background1 from "../assets/images/summer.jpg";
import Background2 from "../assets/images/winter.jpg";
const compare = (arr1, arr2) => {
let inBoth = [];
arr1.forEach(e1 =>
arr2.forEach(e2 => {
if (e1 === e2) {
inBoth.push(e1);
}
})
);
return inBoth;
};
class Topics extends Component {
constructor(props) {
super(props);
this.props.fetchTopics();
this.state = {
newTopic: "",
selectedFile: null,
error: ""
};
}
onFollowClick = topicId => {
const { id } = this.props.user;
this.props.followTopic(id, topicId);
};
onUnfollowClick = topicId => {
const { id } = this.props.user;
this.props.unfollowTopic(id, topicId);
};
handleSelectedFile = e => {
console.log(e.target.files[0]);
this.setState({
selectedFile: e.target.files[0]
});
};
createTopicSubmit = e => {
e.preventDefault();
const { newTopic, selectedFile } = this.state;
this.props.createTopic(newTopic.trim(), selectedFile);
this.setState({
newTopic: "",
selectedFile: null
});
};
removeTopicSubmit = topicId => {
this.props.removeTopic(topicId);
};
renderTopics = () => {
const { topics, user } = this.props;
const followedTopics =
topics &&
user &&
compare(topics.map(topic => topic._id), user.followedTopics);
console.log(topics);
return topics.map((topic, i) => {
return (
<Grid.Column className="topic-container" key={topic._id}>
<div
className="topic-image"
style={{
background:
i % 2 === 0 ? `url(${Background1})` : `url(${Background2})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover"
}}
/>
<p className="topic-name">{topic.name}</p>
<div className="topic-follow-btn">
{followedTopics.includes(topic._id) ? (
<Button
icon
color="olive"
onClick={() => this.onUnfollowClick(topic._id)}
>
Unfollow
<Icon color="red" name="heart" />
</Button>
) : (
<Button
icon
color="teal"
onClick={() => this.onFollowClick(topic._id)}
>
Follow
<Icon color="red" name="heart outline" />
</Button>
)}
{/* Should put a warning safety catch on initial click, as to not accidentally delete an important topic */}
{user.isAdmin ? (
<Button
icon
color="red"
onClick={() => this.removeTopicSubmit(topic._id)}
>
<Icon color="black" name="trash" />
</Button>
) : null}
</div>
</Grid.Column>
);
});
};
render() {
const { loading, user } = this.props;
if (loading) {
return (
<Loader active inline="centered">
Loading
</Loader>
);
}
return (
<div>
<h1>Topics</h1>
{user && user.isAdmin ? (
<div>
<h3>Create a New Topic</h3>
<Form
onSubmit={this.createTopicSubmit}
encType="multipart/form-data"
>
<Form.Field>
<input
value={this.state.newTopic}
onChange={e => this.setState({ newTopic: e.target.value })}
placeholder="Create New Topic"
/>
</Form.Field>
<Form.Field>
<label>Upload an Image</label>
<input
type="file"
name="image"
onChange={this.handleSelectedFile}
/>
</Form.Field>
<Button type="submit">Create Topic</Button>
</Form>
</div>
) : null}
<Grid centered>{this.renderTopics()}</Grid>
</div>
);
}
}
const mapStateToProps = state => {
const { loading, topics } = state.topics;
const { user } = state.auth;
return { loading, topics, user };
};
export default requireAuth(
connect(
mapStateToProps,
{ fetchTopics, followTopic, unfollowTopic, createTopic, removeTopic }
)(Topics)
);
TopicActions/createTopic:
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile); //this is still here
// const data = new FormData();
// data.append("image", imageFile);
// data.append("name", topicName);
const data = {
image: imageFile,
name: topicName
};
console.log("DATA TO SEND: ", data); //still shows image file
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
When I send it like this, I receive the following on the back end:
(these are server console.logs)
REQUEST: { image: {}, name: 'NEW TOPIC' }
IMAGE FILE MAYBE? undefined
IMAGE FILES MAYBE? undefined
NO FILE UPLOADED
If I go the new FormData() route, FormData is an empty object, and I get this server error:
POST http://localhost:3090/topics/newTopic net::ERR_EMPTY_RESPONSE
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile);
const data = new FormData();
data.append("image", imageFile);
data.append("name", topicName);
// const data = {
// image: imageFile,
// name: topicName
// };
console.log("DATA TO SEND: ", data); // shows FormData {} (empty object, nothing in it)
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
Solution was to switch to using Firebase instead, and deal with image upload on the React client (this was attempted with cloudinary but with no success). The resulting download url can be saved to the database with the topic name (which is all I wanted from cloudinary) and now it is displaying the correct images along with the topics.

How to upload images using angular 5 and node js?

How to upload images using angular 5 and node js?
Depending on what you want to do, you have extra information on the internet.
Your question is based on uploading images, and the images are files, so you should investigate how to upload files with node js and Angular 5 searching the internet.
For example, in the case of Node JS, look at this code
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);
from https://www.w3schools.com/nodejs/nodejs_uploadfiles.asp
And to Angular 5
<div class="form-group">
<label for="file">Choose File</label>
<input type="file"
id="file"
(change)="handleFileInput($event.target.files)">
</div>
from Angular-5 File Upload
HOW TO UPLOAD AN IMAGE FILE USING ANGULAR 5/6 AND NODEJS
For a long time I had this same question but never found a complete answer that could help me. So, after doing lots of researches I finally ended up with something solid and decided to share. This is a simplification of something I built but yet very detailed example working with an Item model, component, service, route and controller to select, upload and store a picture using latest versions of Angular and NodeJS (currently Angular 6 and NodeJS 8.11) but should work in previous versions too.
You will notice I use Reactive Form, among other things. Please do not hesitate to ask if you don't understand something on your own. I'd he happy to explain. Here we go...
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();
}
}
The mimeType is a validator to limit the user selecting / loading only an image file from a group of image types...
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>
Using some CSS to hide the input type "file" HTML Element because it looks ugly but still necessary to trigger the user browser to open a dialog window to select a file for upload (a nice button is shown instead for a better user experience)...
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);
}
}
Now, in the backend (NodeJS using ExpressJS), picture you have your app.js where, among other things, you reference your connection with the database (here using MongoDB) and middlewares in the following order...
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;
Your itemRoutes items.js file is in a routes folder...
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;
Your ItemController items.js file in a controllers folder...
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
});
});
};
// ...
And finally, the file.js middleware:
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');
I hope this helps.

Resources