Upload a file, is a regular feature of web programming, Every Business needs this facility, We know How to upload a file using JSP/Html as Front-end and Servlet/Struts/Spring MVC as Server end.
But How to achieve the same in Angular 4 /Microservice combination?
In this tutorial, I will show you the same step by step, before that let me clarify one thing, I am assuming you have a basic understanding of Angular 4 and Microservice.
Now directly jump on the problem statement,
I want to create an upload functionality which invokes a FileUpload Microservice and store the profile picture of an Employee.
Let's create an Angular 4 project using Angular.io plugin in eclipse.
after creating the application modify the app.component.ts file under app module.
import { UploadFileService } from './fileUpload.service';
import { Component } from '@angular/core';
import { HttpClient, HttpResponse, HttpEventType } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './view.component.html',
styleUrls: ['./app.component.css'],
providers: [UploadFileService]
})
export class AppComponent {
selectedFiles: FileList;
currentFileUpload: File;
constructor(private uploadService: UploadFileService) {}
selectFile(event) {
this.selectedFiles = event.target.files;
}
upload() {
this.currentFileUpload = this.selectedFiles.item(0);
this.uploadService.pushFileToStorage(this.currentFileUpload).subscribe(event => {
if (event instanceof HttpResponse) {
console.log('File is completely uploaded!');
}
});
this.selectedFiles = undefined;
}
}
In the @Component decorator, I changed the template URL to view.component.html which actually hold the FileUpload form components. After that, I add a UploadService as a Provider which actually post the selected files to Microservice.
Now, I define a method called selectFile, which capture an event(OnChange event in the fileUpload form field) and extract the file from the target form fields, in this case, the File form fields.
Then I add another method called upload which calls the file upload service and subscribe itself ob Observable<HttpResponse>.
Here, is the view.component.html file
<div style="text-align:center">
<label>
<input type="file" (change)="selectFile($event)">
</label>
<button [disabled]="!selectedFiles"
(click)="upload()">Upload</button>
</div>
Here, I just add a file upload field and when we select a file an onchange event will be fired which call the selectFile method and pass that event to it.
Next, I call the upload method.
Let see the file upload service.
import {Injectable} from '@angular/core';
import {HttpClient, HttpRequest, HttpEvent} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class UploadFileService {
constructor(private http: HttpClient) {}
pushFileToStorage(file: File): Observable<HttpEvent<{}>> {
const formdata: FormData = new FormData();
formdata.append('file', file);
const req = new HttpRequest('POST', 'http://localhost:8085/profile/uploadpicture', formdata, {
reportProgress: true,
responseType: 'text'
}
);
return this.http.request(req);
}
}
here I create a Formdata Object and add the uploaded File into it and using angular HTTP I post the form data to a Microservice running on port 8085 and publish a REST endpoint called /profile/uploadpicture
Hooray, We successfully write the UI part for File upload using Angular4.
if you start the Angular(ng serve) it will look like following,
Let's Build the Microservice part, Create a Project called EmployeeFileUpload service in STS or using start.spring.io, Select Eureka client module to register this microservice with Eureka.
After that, rename the application.properties to bootstrap property. add the following entry.
spring.application.name=EmployeePhotoStorageBoard
eureka.client.serviceUrl.defaultZone:http://localhost:9091/eureka
server.port=8085
security.basic.enable: false
management.security.enabled: false
My Eureka server is located on 9091 port, I give a logical name to this Microservice called EmployeePhotoStorageService which runs on 8085 port.
Now I am going to create a Rest Controller which accepts the request from Angular and binds the Multipart Form.
Let see the code snippets of FileUploadController.
package com.example.EmployeePhotoStorageService.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileController {
@Autowired
FileService fileservice;
@CrossOrigin(origins = "http://localhost:4200")// Call from Local Angualar
@PostMapping("/profile/uploadpicture")
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
String message = "";
try {
fileservice.store(file);
message = "You successfully uploaded " + file.getOriginalFilename() + "!";
return ResponseEntity.status(HttpStatus.OK).body(message);
} catch (Exception e) {
message = "Fail to upload Profile Picture" + file.getOriginalFilename() + "!";
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(message);
}
}
}
Few things to notice here, I use a @CrossOrigin annotation by this I instruct Spring to allow the request comes from localhost:4200 , In production, Microservice and Angular app hosted in different domains to allow other domains request we must have to provide the cross-origin annotation, I Autowired a FileUpload service which actually writes the File content into the disk.
Let see the FileService code,
package com.example.EmployeePhotoStorageService.controller;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Service
public class FileService {
private final Path rootLocation = Paths.get("ProfilePictureStore");
public void store(MultipartFile file) {
try {
System.out.println(file.getOriginalFilename());
System.out.println(rootLocation.toUri());
Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename()));
} catch (Exception e) {
throw new RuntimeException("FAIL!");
}
}
}
Here, I create a directory called ProfilePictureStore under the project it is the same level to src folder. Now I copy the file input stream to the location using java.nio's Files.copy() static method.
Now to run this Microservice I have to write the Sring Application boot file. Let see the code.
package com.example.EmployeePhotoStorageService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class EmployeePhotoStorageService {
public static void main(String[] args) {
SpringApplication.run(EmployeePhotoStorageService.class, args);
}
}
Ok, we are all set, Only the last piece is missing from this tutorial that is the pom.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>EmployeeFileUploadService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>EmployeeDashBoardService</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Yeah, That's it, if we run the Microservice and upload a file from angular we can see that file is stored in ProfilePictureStore folder, very easy isn't it?
Conclusion: This is a very simple example or I can say a prototype of file upload, without any validation or passing any information from UI apart from the raw file like comments, file name, tags etc. You can enrich this basic example using the Formdata Object in Angular, Another Observation is, I directly call the Microservice instance from Angular, That is not the case in Production there you have to introduce a Zuul API gateway which accepts the request from Angular do some security checking then communicate with Eureka and route the request to the actual microservice for simplicity I just skip that part.
Post a Comment