ผมเจอคำถามในกลุ่ม Programmer หลายๆ แห่งครับ คำถามก็คือ จะเขียนโปรแกรม เพื่อให้สามารถอัปโหลดไฟล์ซึ่งเป็น web application ซึ่งพัฒนาโดยใช้ Angular ได้อย่างไร บทความนี้ วิธีที่ผมจะนำเสนอ ก็คือการใช้ Angular เป็น Frontend และใช้ Golang เป็น Backend ครับ เอาละครับ มาเริ่มกันเลยครับ
Note ตัวอย่างจากบทความนี้รันอยู่บน localhost ดังนี้ การจะเอาไปใช้จริงบน production ก็จะต่างจากนี้อยู่พอสมควรนะครับ ถือว่าเอาไว้เป็นไอเดียละกันครับ
Http Server
เราจะเริ่มจากการสร้าง http server เพื่อใช้รับ request จาก angular ครับ ซึ่งในตัวอย่างนี้ ผมจะสร้าง endpoint ที่ชื่อ ‘/upload’ เอาไว้อัพโหลดไฟล์ และผมจะรัน service บน port 3000 ครับ
ก่อนอื่นเลย คือสร้าง folder เพื่อเก็บ source code ก่อนครับ
mkdir go-upload-example
cd go-upload-example
จากนั้นสร้างไฟล์ main.go และเขียนโค๊ดตามนี้ครับ
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
const serverPort = 3000
const uploadFileDirectory = "uploads"
// IResponse response interface
type IResponse struct {
Message string `json:"message"`
}
func jsonResponse(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(data)
}
func uploadFileHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("[uploadFileHandler] start working")
// Allow upload fron cross origin
w.Header().Set("Content-Type", "text/html; charset=ascii")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type,access-control-allow-origin, access-control-allow-headers")
// FormFile returns the first file for the given key `myFile`
// it also returns the FileHeader so we can get the Filename,
// the Header and the size of the file
file, handler, err := r.FormFile("file")
if err != nil {
jsonResponse(w, IResponse{Message: "failed"})
return
}
defer file.Close()
fmt.Printf("Uploaded File: %+v\n", handler.Filename)
fmt.Printf("File Size: %+v\n", handler.Size)
fmt.Printf("MIME Header: %+v\n", handler.Header)
// Create a temporary file within our temp-images directory that follows
// a particular naming pattern
tempFile, err := ioutil.TempFile(uploadFileDirectory, "upload-*.png")
if err != nil {
fmt.Println(err)
}
defer tempFile.Close()
// read all of the contents of our uploaded file into a
// byte array
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
jsonResponse(w, IResponse{Message: "failed"})
return
}
// write this byte array to our temporary file
tempFile.Write(fileBytes)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
jsonResponse(w, IResponse{Message: "success"})
}
func main() {
http.HandleFunc("/upload", uploadFileHandler)
fmt.Println("Server started at port : ", serverPort)
http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil)
}
Method `uploadFileHandler` จะรับ request ที่รับมาเป็น Form Data ครับ เมื่ออ่านข้อมูลได้แล้ว ระบบจะบันทึกไฟล์ไว้ที่โฟลเดอร์ที่ชื่อว่า uploads ครับ ดังนั้นเราต้องสร้างโฟลเดอร์ไว้เก็บไฟล์ ดังนี้ครับ
mkdir uploads
จากนั้น จะ run service โดยใช้คำสั่งนี้ครับ
go run main.go
ถ้าไม่มีอะไรผิดพลาด เราจะเห็นผลลัพธ์จากการรันแบบนี้ http server แบบนี้ ก็แปลว่า server พร้อมใช้งานแล้วครับ
Server started at port : 3000
Angular Frontend
หลังจากที่ web service พร้อมแล้ว จากขั้นตอนที่ 1 อันดับต่อไป ผมจะสร้างเว็บไซต์ที่ใช้ในการอัปโหลดไฟล์ไปที่ web service ครับ โดยเริ่มจากการสร้างเว็บไซต์โดยใช้ angular โดยใช้คำสั่งนี้ครับ
ng new ng-upload-example
ที่ app component ผมจะทำการแก้ไขไฟล์ ดังนี้ครับ
app.component.html
<div class="container">
<form class="upload">
<div class="file-container">
<input class="file" type="file" (change)="onFileChanged($event)" />
<img src="https://simpleicon.com/wp-content/uploads/cloud-upload-2.svg" />
</div>
<div class="detail" *ngIf="file">
{{ file.name }}
</div>
<div class="detail" *ngIf="!file">No file selected</div>
<div class="message" *ngIf="message !== ''">
{{ message }}
</div>
<div class="buttons">
<input
type="button"
value="upload"
[disabled]="!file"
(click)="uploadFile()"
/>
</div>
</form>
</div>
app.component.ts
import { AppService } from './../services/app.service';
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
title = 'ng-upload-example';
file: any;
message = '';
constructor(private as: AppService) {}
onFileChanged(event: any) {
if (!event) {
return;
}
if (event.target.files.length <= 0) {
return;
}
this.file = event.target.files[0];
}
uploadFile() {
const fd = new FormData();
fd.append('file', this.file);
this.message = '';
this.as.uploadFile(fd).subscribe(
(res) => {
this.message = res + '';
},
(err) => {
console.log(err);
this.message = 'Failed';
}
);
}
}
จากนั้น ปรับแต่ style ซักหน่อยครับ
app.component.scss
.container {
width: 100%;
height: 100%;
}
.upload {
width: 500px;
padding: 50px;
margin: 100px auto;
border: 1px solid #eeeeee;
text-align: center;
}
.buttons,
.detail {
margin-top: 20px;
}
.file-container {
width: 150px;
height: 150px;
border: 1px dashed #cccccc;
margin: 0 auto;
position: relative;
}
.file-container .file {
z-index: 1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
opacity: 0;
}
.file-container img {
top: 50%;
left: 50%;
width: 80px;
position: absolute;
transform: translate(-50%, -50%);
z-index: 0;
}
เอาละครับ ในส่วนของ app component ก็เสร็จแล้ว ถ้าดูไฟล์ app.component.ts จะเห็นว่า มันเรียกไปหา AppService เพื่อใช้เชื่อมต่อ API เราจะสร้างไฟล์นั้นต่อครับ โดยใช้คำสั่งต่อไปนี้ครับ เพื่อสร้าง service ครับ
ng g s app
จากนั้น แก้ไขไฟล์ service.ts ดังนี้ครับ
app.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class AppService {
api = 'http://localhost:3000/';
constructor(private http: HttpClient) {}
uploadFile(fd: FormData) {
return this.http.post(this.api + 'upload', fd);
}
}
อันดับสุดท้าย แก้ไฟล์ app.module ดังนี้ครับ เพื่อให้ angular มัน import http client module มาใช้เชื่อมต่อ api ครับ
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
ทดสอบ
เอาละครับ เราได้สร้างทั้ง Frontend และ Backend เรียบร้อยแล้ว ทีนี้เรามาทดสอบกันครับ โดยใช้คำสั่ง
ng serve
เมื่อรันเสร็จแล้ว จะได้ผลลัพธ์แบบนี้ครับ แล้วลองกดทดสอบดู ก็ควรจะเห็นผลลัพธ์แบบนี้ครับ
เย้!! เป็นอันว่าเสร็จเรียบร้อยแล้วนะครับ การเขียนอัพโหลดไฟล์ด้วย Angular คราวหลังจะมีบทความใดมาฝากอีก ก็แวะมาเยี่ยม blog ของผมได้นะครับ ขอบคุณครับ