file uploading gives file name undefined in Mean stack - node.js

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" />

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?

MEAN - Http Put Request 404 Not Found

I am building a MEAN stack application. I am receiving a 404 response when submitting a put request.This only occurs when I am trying to edit a booking. It does not successfully edit a specific booking.
Client Side
booking.service.ts
getBooking(id: string) {
return this.http.get<{ _id: string, title: string, content: string }>("http://localhost:3000/api/bookings/" + id);
}
updateBooking(id: string, title: string, content: string){
const booking: Booking = { id: id, title: title, content: content };
this.http.put("http://localhost:3000/api/bookings/" + id, booking)
.subscribe((response) => {
const id = booking.id;
const updateBook = [...this.bookings];
const oldBookingIndex = updateBook.findIndex(b => b.id === id);
updateBook[oldBookingIndex] = booking;
this.bookings = updateBook;
this.bookingUpdate.next([...this.bookings]);
});
}
booking-create.component.html
<mat-card>
<form (submit)="onSaveBooking(bookingForm)" #bookingForm="ngForm">
<mat-form-field>
<input matInput type="date" name="title" [ngModel]="booking?.title"
required minlength="3"
#title="ngModel"
placeholder="Date">
<mat-error *ngIf="title.invalid">Please enter title</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput name="content" [ngModel]="booking?.content" required
#content="ngModel" placeholder="Golf Course"></textarea>
<mat-error *ngIf="content.invalid">Please enter content</mat-error>
</mat-form-field>
<hr>
<button mat-button
color="accent"
type="submit">Save Booking</button>
</form>
</mat-card>
booking-create.component.ts
import { Component, OnInit } from '#angular/core';
//import {Booking } from '../booking-list/booking.model';
import { NgForm } from '#angular/forms';
import { BookingService } from '../booking.service';
import { ActivatedRoute, ParamMap } from '#angular/router';
import { Booking } from '../booking-list/booking.model';
#Component({
selector: 'app-booking-create',
templateUrl: './booking-create.component.html',
styleUrls: ['./booking-create.component.css']
})
export class BookingCreateComponent implements OnInit {
enteredTitle = '';
enteredContent = '';
booking: Booking;
private mode = 'create';
private bookingId: string;
constructor(public bookingService: BookingService, public route: ActivatedRoute) { }
ngOnInit() {
this.route.paramMap.subscribe((paramMap: ParamMap) => {
if (paramMap.has('bookingId')){
this.mode = 'edit';
this.bookingId = paramMap.get('bookingId');
this.bookingService.getBooking(this.bookingId).subscribe(bookingData => {
this.booking = {id: bookingData._id, title: bookingData.title, content: bookingData.content};
});
}
else {
this.mode = 'create';
this.bookingId = null;
}
});
}
//bookingCreated = new EventEmitter<Booking>();
onSaveBooking(form: NgForm){
if(form.invalid){
return;
}
if (this.mode === 'create') {
this.bookingService.addBooking(form.value.title, form.value.content);
}
else {
this.bookingService.updateBooking(this.bookingId, form.value.title, form.value.content);
}
form.resetForm();
}
}
Server side
app.js
app.get("/api/bookings/:id", (req, res, next) => {
Booking.findById(req.params.id).then(booking => {
if (booking) {
res.status(200).json(booking);
}
else {
res.status(404).json({ message: 'Booking not found'});
}
})
});
app.put("/api/bookings/:id", (req, res, next) => {
const booking = new Booking({
_id: req.body.id,
title: req.body.title,
content: req.body.content
});
Booking.updateOne({_id: req.params.id}, booking).then(result => {
console.log(result);
res.status(200).json({message: 'Booking updated'});
});
});
I guess when you are use using 'booking' in this line that throws an issue
Booking.updateOne({_id: req.params.id}, booking).then(result => {
You can try just to use a single params to see if it is working
{$set: {"title": req.body.title}}
Not sure if the whole object can be passed like that.

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;

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.

How to save image in local folder using Angular2?

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.

Resources