I have created a simple booking app where using the MEAN stack. The app is working fine for the GET functionality. But for some reason my PUT code will just not run i.e. I have added console.log to try and write to the console when the PUT code runs. But this code is not being hit at all. I have tried using Postman to test if the functionality works and Postman works. See my code below:
export class Tank {
id: string;
date: string;
tankNumber: number;
sessionOne: boolean;
sessionTwo: boolean;
sessionThree: boolean;
sessionFour: boolean;
sessionFive: boolean;
constructor(
id: string,
date: string,
tankNumber: number,
sessionOne: boolean,
sessionTwo: boolean,
sessionThree: boolean,
sessionFour: boolean,
sessionFive: boolean
) {
this.id = id;
this.date = date;
this.tankNumber = tankNumber;
this.sessionOne = sessionOne;
this.sessionTwo = sessionTwo;
this.sessionThree = sessionThree;
this.sessionFour = sessionFour;
this.sessionFive = sessionFive;
}
}
Below is my HTML code that sends the booking request:
<mat-card *ngIf="dateSelectionButtonPressed">
<mat-card-header>
<mat-card-title>Tank 1</mat-card-title>
</mat-card-header>
<mat-card-content *ngFor="let session of tankAvailability; let i = index">
<div class="tank-one" *ngIf="session.tankNumber == 1">
<button *ngIf="session.sessionOne === false" mat-raised-button color="primary" (click)="bookSession(session.id, session.date, session.tankNumber, session.sessionOne = true, session.sessionTwo, session.sessionThree, session.sessionFour, session.sessionFive)">07.00</button>
<button *ngIf="session.sessionTwo === false" mat-raised-button color="primary" (click)="bookSession(session.id, session.date, session.tankNumber, session.sessionOne, session.sessionTwo = true, session.sessionThree, session.sessionFour, session.sessionFive)">09.00</button>
<button *ngIf="session.sessionThree === false" mat-raised-button color="primary" (click)="bookSession(session.id, session.date, session.tankNumber, session.sessionOne, session.sessionTwo, session.sessionThree = true, session.sessionFour, session.sessionFive)">11.00</button>
<button *ngIf="session.sessionFour === false" mat-raised-button color="primary" (click)="bookSession(session.id, session.date, session.tankNumber, session.sessionOne, session.sessionTwo, session.sessionThree, session.sessionFour = true, session.sessionFive)">13.00</button>
<button *ngIf="session.sessionFive === false" mat-raised-button color="primary" (click)="bookSession(session.id, session.date, session.tankNumber, session.sessionOne, session.sessionTwo, session.sessionThree, session.sessionFour, session.sessionFive = true)">15.00</button>
</div>
</mat-card-content>
</mat-card>
Below is my booking code:
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Tank } from '../../models/tank.model';
import { FormGroup, FormControl } from '#angular/forms';
import { TankService } from 'src/app/services/tank.service';
#Component({
selector: 'app-booking',
templateUrl: './booking.component.html',
styleUrls: ['./booking.component.css']
})
export class BookingComponent implements OnInit, OnDestroy {
private tankSub: Subscription;
// public payPalConfig?: IPayPalConfig;
selectedDate: any;
convertedDate: string;
dateSelectionButtonPressed = false;
tankAvailability: Tank[] = [];
tankOneAvailability: string[] = [];
tank: Tank;
constructor(private tankService: TankService) { }
bookSession(id, date, tankNumber, sessionOne, sessionTwo, sessionThree, sessionFour, sessionFive) {
const tank = new Tank(id, date, tankNumber, sessionOne, sessionTwo, sessionThree, sessionFour, sessionFive);
this.tankService.updateTankWithNewBooking(tank);
}
}
Below is my service for sending the Tank object along with ID to the back end server:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Tank } from '../models/tank.model';
#Injectable({
providedIn: 'root'
})
export class TankService {
private tanks: Tank[] = [];
private tanksUpdate = new Subject<Tank[]>();
constructor(private httpClient: HttpClient) { }
// Update tank with new booked session
updateTankWithNewBooking(tank: Tank) {
console.log(tank);
return this.httpClient.put<{ message: string }>('http://localhost:3000/api/tank/' + tank.id, tank);
}
}
Below is my backend server side NodeJS code:
const express = require('express');
const bodyParser = require('body-parser')
const mongoose = require('mongoose');
const TankSchemaModel = require('./models/tank');
const app = express();
mongoose.connect('xxxxxxx', { useNewUrlParser: true })
.then(() => {
console.log('Connected to database!');
})
.catch((err) => {
console.log(err + 'Connection failed!!');
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, OPTIONS"
);
next();
});
// create a tank
app.post('/api/tank', (req, res, next) => {
const tank = new TankSchemaModel({
date: '22-08-2019',
tankNumber: 1,
sessionOne: false,
sessionTwo: true,
sessionThree: false,
sessionFour: false,
sessionFive: true
/* date: req.body.date,
tankNumber: req.body.tankNumber,
sessionOne: req.body.sessionOne,
sessionTwo: req.body.sessionTwo,
sessionThree: req.body.sessionThree,
sessionFour: req.body.sessionFour,
sessionFive: req.body.sessionFive */
});
// save tank to database
tank.save().then(createdTankDetails => {
res.status(201).json({
message: 'Tank detals created successfully!',
tankId: createdTankDetails._id
});
});
});
// get all tank data
app.get('/api/tanks', (req, res, next) => {
TankSchemaModel.find().then(documents => {
res.status(200).json({
message: 'Tank data fetched successfully',
tanks: documents
});
});
});
// get tank data by date
app.get('/api/tanks/:date', (req, res, next) => {
TankSchemaModel.find( { date: { $eq: req.params.date } } ).then(documents => {
res.status(200).json({
message: 'Tank details successfully fetched!',
tanks: documents
});
});
});
// edit a tank
app.put('/api/tank/:id', (req, res, next) => {
console.log("Beans!");
console.log(req.params.id);
console.log(req.body);
console.log(req.body.date);
TankSchemaModel.findOneAndUpdate({ _id: req.params.id },
{
$set: {
// _id: req.body.id,
date: req.body.date,
tankNumber: req.body.tankNumber,
sessionOne: req.body.sessionOne,
sessionTwo: req.body.sessionTwo,
sessionThree: req.body.sessionThree,
sessionFour: req.body.sessionFour,
sessionFive: req.body.sessionFive
}
}, (err, result) => {
if(err) {
console.log(err);
res.status(401).json({
message: 'Tank booking failed!'
});
} else {
res.status(200).json({
message: 'Tank booking updated successfully!'
});
}
});
});
module.exports = app;
Related
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?
I am trying to learn how to make reservation but i am stuck at the first step.
In particular i created a Redux action that sends the picked date from frontend to backend but it doesn't seem to reach the database.
This is my code:
Model:
import mongoose from 'mongoose';
var daySchema = new mongoose.Schema({
date: { type: Date }
});
const Day = mongoose.model('Day', daySchema);
export default Day;
Router:
import express from 'express';
import expressAsyncHandler from 'express-async-handler';
import Day from '../models/day.js';
const dayRouter = express.Router();
dayRouter.post(
'/day',
expressAsyncHandler(async (req, res) => {
const date = new Day(req.body.date);
const chosenDay = await date.save();
if (chosenDay) {
res.send({
date: chosenDay
});
} else {
res.status(401).send({ message: 'unable to save to database' });
}
})
);
export default dayRouter;
Redux-action:
import Axios from 'axios';
import { DATE_CREATE_FAIL, DATE_CREATE_REQUEST, DATE_CREATE_SUCCESS } from '../constants/dateConstants';
export const createDate = (date) => async (dispatch) => {
try {
dispatch({ type: DATE_CREATE_REQUEST });
const { data } = await Axios.post(
'/api/day',
date,
);
dispatch({
type: DATE_CREATE_SUCCESS,
payload: data,
success: true,
});
} catch (error) {
dispatch({
type: DATE_CREATE_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Redux-reducer:
import { DATE_CREATE_FAIL, DATE_CREATE_REQUEST, DATE_CREATE_SUCCESS } from '../constants/dateConstants';
export const dateCreateReducer = (state = {}, action) => {
switch (action.type) {
case DATE_CREATE_REQUEST:
return { loading: true };
case DATE_CREATE_SUCCESS:
return { loading: false, date: action.payload, success: true };
case DATE_CREATE_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
Redux-constants:
export const DATE_CREATE_REQUEST = 'DATE_CREATE_REQUEST';
export const DATE_CREATE_SUCCESS = 'DATE_CREATE_SUCCESS';
export const DATE_CREATE_FAIL = 'DATE_CREATE_FAIL';
Server:
import http from 'http';
import express from 'express';
import path from 'path';
import mongoose from 'mongoose';
import bodyParser from 'body-parser';
import config from './config.js';
import dayRouter from './routers/dayRouter.js';
const mongodbUrl = config.MONGODB_URL;
mongoose
.connect(mongodbUrl, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
})
.then(() => console.log('db connected.'))
.catch((error) => console.log(error.reason));
const app = express();
app.use(bodyParser.json());
app.use('/api/day', dayRouter);
const __dirname = path.resolve();
app.use(express.static(path.join(__dirname, '/frontend/build')));
app.get('*', (req, res) => {
res.sendFile(path.join(`${__dirname}/frontend/build/index.html`));
});
app.use((err, req, res, next) => {
res.status(500).send({ message: err.message });
});
const httpServer = http.Server(app);
httpServer.listen(config.PORT, () => {
console.log(`Server started at http://localhost:${config.PORT}`);
});
React component:
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import { makeStyles } from '#material-ui/core/styles';
import DatePicker from 'react-datepicker';
import setHours from "date-fns/setHours";
import setMinutes from "date-fns/setMinutes";
import { useDispatch } from 'react-redux';
import { createDate } from '../actions/dateAction';
function DayReservationScreen() {
const [startDate, setStartDate] = useState(
setHours(setMinutes(new Date(), 30), 16)
);
const dispatch = useDispatch();
const submitHandler = (e) => {
e.preventDefault();
dispatch(
createDate({
date: startDate
})
);
console.log(startDate)
};
return (
<div>
<form onSubmit={submitHandler}>
<DatePicker
selected={startDate}
onChange={date => setStartDate(date)}
showTimeSelect
/* showTimeSelectOnly */
timeIntervals={15}
excludeTimes={[
setHours(setMinutes(new Date(), 0), 17),
setHours(setMinutes(new Date(), 30), 18),
setHours(setMinutes(new Date(), 30), 19),
setHours(setMinutes(new Date(), 30), 17)
]}
dateFormat="MMMM d, yyyy h:mm aa"
/>
<Button
variant="contained"
size="small"
color="primary"
style={{
width: 80,
margin: 5
}}
className={classes.button}
type='submit'
>
Submit
</Button>
</form>
</div>
);
}
export default DayReservationScreen;
What am i doing wrong?
The front-end part is working correctly. But you have server side issues.
You are using the dayRouter at /api/day route and in the router itself you use path /day.
Therefore, the full path to the endpoint will be http://localhost:PORT/api/day/day.
And from the frontend, you send requests to http://localhost:PORT/api/day
Just edit your router:
import express from 'express';
import expressAsyncHandler from 'express-async-handler';
import Day from '../models/day.js';
const dayRouter = express.Router();
dayRouter.post(
'/',
expressAsyncHandler(async (req, res) => {
const date = new Day(req.body.date);
const chosenDay = await date.save();
if (chosenDay) {
res.send({
date: chosenDay
});
} else {
res.status(401).send({ message: 'unable to save to database' });
}
})
);
export default dayRouter;
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.
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.
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.