when i try to upload image on heroku i get this error :
Mixed Content: The page at 'https://twassol.herokuapp.com/' was loaded over HTTPS, but requested an insecure image 'http://twassol.herokuapp.com/images/%D8%A8%D8%B7%D8%A7%D8%B1%D9%8A%D9%82-1564103914328.jpg'. This content should also be served over HTTPS.
and this error:
cross-Origin Read Blocking (CORB) blocked cross-origin response http://twassol.herokuapp.com/images/%D8%A8%D8%B7%D8%A7%D8%B1%D9%8A%D9%82-1564103914328.jpg with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details.
i tried to use "http://twassol.herokuapp.com/" , it upload the image but does't show it.
this's post.service.ts file :
import { HttpClient } from '#angular/common/http';
import { Router } from '#angular/router';
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Post } from './post.model';
import { environment } from '../../environments/environment';
const BACKEND_URL = environment.apiUrl + '/posts/';
#Injectable({providedIn: 'root'})
export class PostsService {
private posts: Post[] = [];
private postsUpdated = new Subject<{posts: Post[], postsCount: number}>();
constructor(private http: HttpClient, private router: Router){}
getPosts(postsPerPage: number, currentPage: number) {
const queryParams = `?pagesize=${postsPerPage}&page=${currentPage}`
this.http.get<{ message: string, posts: any, maxPosts: number }>(BACKEND_URL + queryParams)
.pipe(map( postData => {
return { posts :postData.posts.map(post =>{
return{
title: post.title,
content: post.content,
id: post._id,
imagePath: post.imagePath,
creator: post.creator
}
}), maxPosts: postData.maxPosts};
}))
.subscribe((transformedPostData) => {
this.posts = transformedPostData.posts;
this.postsUpdated.next({posts: [...this.posts], postsCount: transformedPostData.maxPosts});
});
}
getPostUpdateListener(){
return this.postsUpdated.asObservable()
}
getPost(id: string){
return this.http.get<{_id: string, title: string, content: string, imagePath: string, creator: string}>(
BACKEND_URL + id
);
}
addPost(title: string, content: string, image: File){
const postData = new FormData;
postData.append('title', title);
postData.append('content', content);
postData.append('image', image, title);
this.http.post<{message: string, post: Post}>(BACKEND_URL, postData)
.subscribe( responseData => {
this.router.navigate(['/']);
});
}
updatePost(id: string, title: string, content: string, image: string | File){
let postData: Post | FormData;
if (typeof(image) === 'object'){
postData = new FormData;
postData.append('id', id);
postData.append('title', title);
postData.append('content', content);
postData.append('image', image, title);
} else {
postData = {
id: id,
title: title,
content: content,
imagePath: image,
creator: null
}
}
this.http
.put(BACKEND_URL + id, postData)
.subscribe(response => {
this.router.navigate(['/']);
});
}
deletePost(postId: string){
return this.http.delete(BACKEND_URL + postId)
}
}
convert your image in to base64 string and pass it in the post request
html:
<button type="button" (click)="upload.click()" >
<span>Upload Photo</span>
</button>
<input #upload formControlName="imageData" type="file" name="imageData" accept='image/*' (change)="getFiles($event)"style="display:none;">
ts:
getFiles(event) {
this.files = event.target.files;
var reader = new FileReader();
reader.onload = this._handleReaderLoaded.bind(this);
reader.readAsBinaryString(this.files[0]);
}
_handleReaderLoaded(readerEvt) {
var binaryString = readerEvt.target.result;
this.base64DataOfImage= btoa(binaryString);
this.{{your Model}}.imageData = this.base64DataOfImage
this.fileSrc = 'data:image/png;base64,'+this.base64DataOfImage;
}
retriving data back from service
html
<img height="200" [src]="fileSrc" />
ts
//service call(assuming the returned as {"imageData":"base64data"})
this.fileSrc='data:image/png;base64,'+responce.imageData;
make sure you increse your request body size in backend
app.use(bodyParser.json({limit: "50mb"}));
app.use(bodyParser.urlencoded({limit: "50mb", extended: true, parameterLimit:50000}));
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 getting an error everytime I submit my post without an image, submission of the image is optional because I run an if condition before appending the imagePath. working on a MEAN stack up to which I am new in.
mime-type-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 as ArrayBuffer).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 "ffd8ffe0":
case "ffd8ffe1":
case "ffd8ffe2":
case "ffd8ffe3":
case "ffd8ffe8":
isValid = true;
break;
default:
isValid = false; // Or you can use the blob.type as fallback
break;
}
if (isValid) {
observer.next(null);
} else {
observer.next({ invalidMimeType: true });
}
observer.complete();
});
fileReader.readAsArrayBuffer(file);
}
);
return frObs;
};
create-post-component
import { Component, Inject, Input, OnInit} from '#angular/core';
import { ActivatedRoute, ParamMap } from '#angular/router';
import { FormControl, FormGroup, Validators } from '#angular/forms'
import { PostModel } from 'src/app/Models/post.model';
import { PostsService } from 'src/app/Services/posts.service';
import { Router } from '#angular/router'
import { mimeType } from 'src/app/auth/Validators/mime-type.validator'
import Swal from 'sweetalert2';
#Component({
selector: 'app-create-post',
templateUrl: './create-post.component.html',
styleUrls: ['./create-post.component.css']
})
export class CreatePostComponent {
public mode = 'user';
private postId : string;
public post: PostModel;
isLoading : boolean = false;
imagePreview: string;
PostForm = new FormGroup({
title: new FormControl('', [Validators.required, Validators.minLength(1), Validators.maxLength(30)]),
content: new FormControl('', [Validators.required, Validators.minLength(1)]),
image: new FormControl(null, {validators:[], asyncValidators: [mimeType] })
})
constructor(public postsService: PostsService, public route: ActivatedRoute, private router: Router){
}
ngOnInit(){
this.route.paramMap.subscribe((paramMap: ParamMap)=>{
if(paramMap.has('postId')){
this.mode = 'edit';
this.postId = paramMap.get('postId');
this.isLoading = true;
this.postsService.getPost(this.postId).subscribe(postdata =>{
this.isLoading = false;
this.post = {id: postdata._id, title: postdata.title, content: postdata.content , imagePath: postdata.imagePath};
this.PostForm.setValue({ title: this.post.title, content: this.post.content, image: this.post.imagePath });
});
}
else{
this.mode = 'user';
this.postId = null;
}
})
}
onAddPost()
{
if(this.PostForm.invalid){
return;
}
this.isLoading = true;
if (this.mode === 'user'){
this.postsService.addPosts(this.PostForm.value.title, this.PostForm.value.content, this.PostForm.value.image)
this.isLoading = false;
Swal.fire({
position: 'center',
icon: 'success',
title: 'Post added!',
showConfirmButton: false,
timer: 2000
})
}
else{
this.postsService.updatePosts(
this.postId,
this.PostForm.value.title,
this.PostForm.value.content,
this.PostForm.value.image
);
this.router.navigateByUrl('/user');
}
this.PostForm.reset();
}
onImagePicked(event: Event){
const file = (event.target as HTMLInputElement).files[0];
this.PostForm.patchValue({image: file});
this.PostForm.get('image').updateValueAndValidity();
const reader = new FileReader();
reader.onload = () => {
this.imagePreview = reader.result as string;
};
reader.readAsDataURL(file);
}
}
posts-service.ts
import { Injectable } from '#angular/core';
import { PostModel } from '../Models/post.model';
import { HttpClient } from '#angular/common/http'
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators'
#Injectable(
{ providedIn: 'root' }
)
export class PostsService {
constructor(private http: HttpClient) { }
private posts: PostModel[] = [];
private postsUpdated = new Subject<PostModel[]>()
getPosts() {
this.http.get<{ message: string, posts: any }>('http://localhost:3300/api/posts')
.pipe(
map((postData) => {
return postData.posts.map(post => {
return {
title: post.title,
content: post.content,
id: post._id,
imagePath: post.imagePath
}
})
}))
.subscribe(transformedPosts => {
this.posts = transformedPosts;
this.postsUpdated.next([...this.posts]) //updating the posts so that it is available to the rest of the app
})
}
getUpdatedPostsListener() {
return this.postsUpdated.asObservable()
}
getPost(id: string) {
// return {...this.posts.find( p => p.id === id)} //p implies each post, and p.id implies that post's id
return this.http.get<{ _id: string; title: string; content: string, imagePath: string }>("http://localhost:3300/api/posts/" + id);
}
addPosts(title: string, content: string, image: File) {
const postData = new FormData();
postData.append('title', title);
postData.append('content', content);
if(image != undefined){
postData.append('image', image, title);
}
this.http.post<{ message: string, post: PostModel }>('http://localhost:3300/api/posts', postData).subscribe((responseData) => {
const post: PostModel = { id: responseData.post.id, title: title, content: content, imagePath: responseData.post.imagePath }
this.posts.push(post);
this.postsUpdated.next([...this.posts]);
})
}
updatePosts(id: string, title: string, content: string, image: File | string) {
let postData: PostModel | FormData;
if (typeof image === "object") {
postData = new FormData();
postData.append("id", id);
postData.append("title", title);
postData.append("content", content);
if(image != undefined){
postData.append("image", image, title);
}
} else {
postData = {
id: id,
title: title,
content: content,
imagePath: image
};
}
this.http
.put("http://localhost:3300/api/posts/" + id, postData)
.subscribe(response => {
const updatedPosts = [...this.posts];
const oldPostIndex = updatedPosts.findIndex(p => p.id === id);
const post: PostModel = {
id: id,
title: title,
content: content,
imagePath: ""
};
updatedPosts[oldPostIndex] = post;
this.posts = updatedPosts;
this.postsUpdated.next([...this.posts]);
// this.router.navigate(["/"]);
});
}
deletePosts(postId: string) {
this.http.delete('http://localhost:3300/api/posts/' + postId)
.subscribe(() => {
const updatedPosts = this.posts.filter(post => post.id !== postId);
this.posts = updatedPosts;
this.postsUpdated.next([...this.posts]);
})
}
}
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" />
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;
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.