เนื้อหาในตอนที่ 6 นี้เราจะมาดูในเรื่องการใช้งาน Advance HttpClient ใน Anuglar ส่วนของ
การใช้งาน progress event กรณีที่ใช้งานร่วมกับการอัพโหลดไฟล์ที่มีขนาดใหญ่ และเราต้องการ
ที่จะแสดงสถานะหรือความคืบหน้าของการอัพโหลด เช่นว่า ขณะนี้อัพโหลดไปแล้วคิดเป็นกี่เปอร์เซ็น
เป็นต้น
ในตัวอย่างการประยุกต์และการใช้งานที่จะนำเสนอต่อไปนี้ จะมีด้วยกัน 2 รูปแบบ แบบแรกคือการเลือก
เฉพาะไฟล์สำหรับอัพโหลด โดยไม่มีการส่งข้อมูลใดๆ นอกจากไฟล์ที่ต้องการอัพโหลด และมีการใช้งาน
progress event หรือการใช้งาน event ที่แสดงถึงความคืบหน้าของอัพโหลดไฟล์ ส่วนแบบที่สองเราจะเป็น
การใช้งานกรณีส่งข้อมูลรูปภาพไปพร้อมกับข้อมูลของ element form อื่นเพิ่มเติมไปด้วย โดยในที่นี้เราจะ
ส่งข้อมูลจาก input text และก็ไฟล์อัพโหลดไปใช้งานพร้อมกัน โดยวิธีที่สองนี้จะไม่มีการใช้งาน progress event
สำหรับในตัวอย่าง เราจะใช้รูปแบบของ input file ของ bootstrap 4 โดยจะยกตัวอย่างเป็นการอัพโหลด
เป็นไฟล์รูปแบบ มีการแสดง preview รูปภาพที่กำลังจะอัพโหลดด้วย
การอัพโหลดไฟล์ร่วมกับ การใช้งาน Progress Event
เริ่มต้นกันที่ไฟล์ register.component.css กำหนด css class สำหรับใช้งานดังนี้
ไฟล์ register.component.css
#place_previewImg{ margin-bottom: 10px; }
ไฟล์ register.component.html
<p> register works!</p> <pre>{{ responseValue | json }}</pre> <pre>{{ registForm.value | json }}</pre> <form (ngSubmit)="onSubmit(registForm)" #registForm="ngForm" novalidate> <div class="form-group row"> <label for="file2" class="col-3 col-form-label text-right"> อัพโหลดไฟล์ </label> <div class="col-6"> <label class="custom-file"> <input (change)="onChangePic($event)" type="file" name="file2" id="file2" class="custom-file-input" accept="image/*"> <span class="custom-file-control">{{ chooseFile }}</span> </label> </div> <div class="offset-3"></div> </div> <div class="form-row"> <div class="col-9 offset-3"> <img id="place_previewImg" *ngIf="previewLoaded" [src]="previewImg" style="height:150px;"> </div> </div> <div class="form-row"> <div class="col-9 offset-3"> <button type="submit" [disabled]="!registForm.valid" class="btn btn-primary">สมัครสมาชิก</button> </div> </div> </form> <br>
ประยุกต์จากฟอร์มของบทความก่อนหน้า เราจะมีแค่ input type file สำหรับอัพโหลไฟล์รุปภาพ
<input (change)="onChangePic($event)" type="file" name="file2" id="file2" class="custom-file-input" accept="image/*">
มีการกำหนด onChange event เข้าไปสำหรับทำการแสดงรูปภาพ preview ผ่านฟังก์ชั่น onChangePic($event)
โดยส่งค่า Event Object เข้าไปในฟังก์ชั่นด้วย เมื่อมีการเลือกไฟล์รูปภาพ ก็จะให้แสดงชื่อไฟล์รูปภาพผ่านตัวแปร
chooseFile
<span class="custom-file-control">{{ chooseFile }}</span>
และแสดงรูป prview ในส่วนของแท็ก img
<img id="place_previewImg" *ngIf="previewLoaded" [src]="previewImg" style="height:150px;">
หน้าตาของส่วนของฟอร์มอัพโหลดไฟล์ จะได้เป็นดังนี้
ตัวอย่างเมื่อเลือกไฟล์ (กรณีเพิ่มโค้ด javascript แล้ว)
ต่อไปดูในส่วนของไฟล์การทำงานเพื่อทำการอัพโหลดไฟล์ให้เราปรับไฟล์ register.component.ts เป็นดังนี้
ไฟล์ register.component.ts
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, ParamMap } from '@angular/router'; import { HttpClient, HttpHeaders, HttpErrorResponse, HttpRequest, HttpEventType, HttpResponse, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; @Component({ // selector: 'app-register', templateUrl: './register.component.html', styleUrls: ['./register.component.css'] }) export class RegisterComponent implements OnInit { public provinces:string[] = ['กรุงเทพฯ','นนทบุรี']; public hobbies:string[] = ['ออกกำลังกาย','อ่านหนังสือ']; public urlApi:string = "http://localhost/demo/"; public responseValue:any; public chooseFile:string = ''; public previewImg:any; public previewLoaded:boolean = false; public fileList:any[]; public fileupload:any; constructor( private http:HttpClient, private route: ActivatedRoute, private router:Router ) { } onSubmit(f:any){ let data = f.value; // ในที่นี้ค่านี้ยังไม่ใช้ เพราะเราจะส่งแค่ไฟล์ไปอย่างเดียว let file:any = this.fileupload; // กำหนด file Object mี่จะทำการอัพโหลด const req = new HttpRequest('POST',this.urlApi+'show_data.php', file, { reportProgress: true, // กำหนด reportProgress เพื่อใ้ช้งาน prgress event }); this.http.request(req).subscribe(event => { // โดยรูปแบบการอัพโหลดด้วยวิธีนี้จะเป็นการส่งข้อมูลแบบ event stream // ที่เราสามารถดูความคืบหน้าการอัพโหลดได้ if (event.type === HttpEventType.UploadProgress) { // ขณะกำลังอัพโหลด // ทดสอบแสดงสถานะการอัพโหลดผ่่านทาง console const percentDone = Math.round(100 * event.loaded / event.total); console.log(`File is ${percentDone}% uploaded.`); } else if (event instanceof HttpResponse) { // แสดงสถานะอัพโหลดสำเร็จแล้วทาง console console.log('File is completely uploaded!'); this.fileupload = null; this.chooseFile = ''; this.previewLoaded = false; } }); // การตรวจสอบการเลือกไฟล์และแสดงชื่อไฟล์ onChangePic(e:any){ console.log(e); let file = e.dataTransfer ? e.dataTransfer.files[0] : e.target.files[0]; this.fileupload = file; this.fileList = e.target.files; console.log(file); console.log(typeof file); if(typeof(file)=='undefined'){ console.log("Please choose file"); this.chooseFile = ''; this.previewImg = ''; this.previewLoaded = false; }else{ this.chooseFile = file.name; this.previewThumbnail(file); } } // ส่วนของการแสดงรูป preview previewThumbnail(file:any){ let imageType = /image.*/ if(!file.type.match(imageType)){ alert('invalid format'); return; } let reader = new FileReader(); reader.onload = this._handleReaderLoaded.bind(this); reader.readAsDataURL(file); } // ส่วนของการส่ง dataUti ของรุปไปแสดงเป็น preview _handleReaderLoaded(e) { this.previewImg = e.target.result; this.previewLoaded = true; } ngOnInit() { } }
คำอธิบายแสดงในโค้ด เรามีการกำหนด option ให้มีการใช้งาน progress event ผ่านการกำหนดค่า reportProgress: true
ผลลัพธ์ของการทำงานของโค้ดข้างต้นดังแสดงในรุปตัวด้านล่าง
ต่อไปส่วนของฝั่ง server หรือ backend service ที่เราทำการรับค่าและบันทึกไฟล์ที่ได้ทำการอัพโหลด
ในตัวอย่างเราส่งไปที่ไฟล์ show_data.php ให้เราปรับในส่วนของไฟล์นี้เป็นดังนี้
ไฟล์ show_data.php ไฟล์รับค่าฝั่ง server
<?php header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Headers: Content-Type,X-Custom-Header'); header('Content-type: application/json'); $jsonData = array(); if($_SERVER['REQUEST_METHOD'] == "GET") { echo json_encode($jsonData); }elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS"){ echo json_encode($jsonData); }elseif($_SERVER['REQUEST_METHOD'] == "POST"){ $uploadData = file_get_contents('php://input'); //$vars=parse_str($res,$post_vars); $headers = getallheaders(); // https://www.freeformatter.com/mime-types-list.html $mime_types = array( 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'bmp' => 'image/bmp', 'ico' => 'image/vnd.microsoft.icon', 'tiff' => 'image/tiff', 'tif' => 'image/tiff', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', ); $ext = array_search($headers['Content-Type'], $mime_types); $filename = time().".".$ext; file_put_contents("upload/".$filename,$uploadData); echo json_encode(array( "statusCode"=>"200 OK", "statusMessage"=>"Successful" )); }else{ header("HTTP/1.1 403 Access Forbidden"); echo json_encode(array( "statusCode"=>"403 Access Forbidden", "statusMessage"=>"Fail" )); } ?>
ทดสอบการทำงาน สังเกตจากรูปด้านล่าง เมื่อมีการอัพโหลดไฟล์เรียบร้อยแล้ว เราทำการล้างค่าข้อมูลเก่า
ในฟอร์มจะไม่มีการแสดงรุปภาพ preview แล้ว พร้อมสำหรับการอัพโหลดไฟล์ใหม่แล้ว ส่วนของ console
เราจะเห็นว่ามีการแสดง progress event ค่าเป็นเปอร์เซ็นต์การทำงานในตัวอย่างจะเห็นมี 72% แลถก็ 100%
เมื่อมีการอัพโหลดไฟล์สำเร็จ
ตัวอย่างไฟล์รุปภาพที่อัพโหลดสำเร็จแล้ว
เป็นอันเสร็จเรียบร้อยสำหรับแนวทางแรกที่มีการใช้งาน progress event
การอัพโหลดไฟล์พร้อมกับข้อมูลอื่นๆในฟอร์มด้วย FormData Object
ไปต่อที่รูปแบบที่สอง ที่มีการส่งรูปภาพไปพร้อมกับข้อมูลฟอร์มไปบันทึกพร้อมกัน ให้เราปรับไฟล์
register.component.html โดยเพิ่ม input type text เข้าไปดังนี้
ไฟล์ register.component.html
<p> register works!</p> <pre>{{ responseValue | json }}</pre> <pre>{{ registForm.value | json }}</pre> <form (ngSubmit)="onSubmit(registForm)" #registForm="ngForm" novalidate> <div class="form-group row"> <label for="fullname" class="col-3 col-form-label text-right"> ชื่อ นามสกุล </label> <div class="col-6"> <input type="text" class="form-control is-invalid" ngModel #fullname="ngModel" name="fullname" id="fullname" [class.is-valid]="fullname.valid" [class.is-invalid]="!fullname.valid" placeholder="Fullname" required> <div class="invalid-feedback"> Please provide a valid Fullname. </div> </div> <div class="offset-3"></div> </div> <div class="form-group row"> <label for="file2" class="col-3 col-form-label text-right"> อัพโหลดไฟล์ </label> <div class="col-6"> <label class="custom-file"> <input (change)="onChangePic($event)" type="file" name="file2" id="file2" class="custom-file-input" accept="image/*"> <span class="custom-file-control">{{ chooseFile }}</span> </label> </div> <div class="offset-3"></div> </div> <div class="form-row"> <div class="col-9 offset-3"> <img id="place_previewImg" *ngIf="previewLoaded" [src]="previewImg" style="height:150px;"> </div> </div> <div class="form-row"> <div class="col-9 offset-3"> <button type="submit" [disabled]="!registForm.valid" class="btn btn-primary">สมัครสมาชิก</button> </div> </div> </form> <br>
จากนั้นปรับโค้ดในส่วนของไฟล์ register.component.ts เป็นดังนี้
ไฟล์ register.component.ts
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, ParamMap } from '@angular/router'; import { HttpClient, HttpHeaders, HttpErrorResponse, HttpRequest, HttpEventType, HttpResponse, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; @Component({ // selector: 'app-register', templateUrl: './register.component.html', styleUrls: ['./register.component.css'] }) export class RegisterComponent implements OnInit { public provinces:string[] = ['กรุงเทพฯ','นนทบุรี']; public hobbies:string[] = ['ออกกำลังกาย','อ่านหนังสือ']; public urlApi:string = "http://localhost/demo/"; public responseValue:any; public chooseFile:string = ''; public previewImg:any; public previewLoaded:boolean = false; public fileList:any[]; public fileupload:any; constructor( private http:HttpClient, private route: ActivatedRoute, private router:Router ) { } onSubmit(f:any){ let data = f.value; let file:any = this.fileupload; let formData:FormData = new FormData(); // กรณีมีการเลือกไฟล์ ส่งไฟล์ไปใช้งานกับ formData if(file && file!='undefined'){ console.log(file); formData.append('uploadFile', file, file.name); } // วนลูปค่าจาก form ที่ส่งเข้ามา ไปใช้งานใน formData for(let key in data ) { formData.append(key, data[key]); } this.http.post(this.urlApi+'show_data.php',formData,{ headers: new HttpHeaders().set('X-Custom-Header', 'my-header'), params: new HttpParams().set('id', '3') }) .subscribe(result =>{ this.responseValue = result; console.log(result); f.reset(); // reset form this.fileupload = null; this.chooseFile = ''; this.previewLoaded = false; }, ( err:HttpErrorResponse ) => { // กรณี error if (err.error instanceof Error) { // กรณี error ฝั่งผู้ใช้งาน หรือ การเชื่อมต่อเกิด error ขึ้น console.log('An error occurred:', err.error.message); }else{ // กรณี error ฝั่ง server ไม่พบไฟล์ ,server error console.log(`Backend returned code ${err.status}, body was: ${err.error}`); } }); } // การตรวจสอบการเลือกไฟล์และแสดงชื่อไฟล์ onChangePic(e:any){ console.log(e); let file = e.dataTransfer ? e.dataTransfer.files[0] : e.target.files[0]; this.fileupload = file; this.fileList = e.target.files; console.log(file); console.log(typeof file); if(typeof(file)=='undefined'){ console.log("Please choose file"); this.chooseFile = ''; this.previewImg = ''; this.previewLoaded = false; }else{ this.chooseFile = file.name; this.previewThumbnail(file); } } // ส่วนของการแสดงรูป preview previewThumbnail(file:any){ let imageType = /image.*/ if(!file.type.match(imageType)){ alert('invalid format'); return; } let reader = new FileReader(); reader.onload = this._handleReaderLoaded.bind(this); reader.readAsDataURL(file); } // ส่วนของการส่ง dataUti ของรุปไปแสดงเป็น preview _handleReaderLoaded(e) { this.previewImg = e.target.result; this.previewLoaded = true; } ngOnInit() { } }
สำหรับการใช้งาน formData นั้นค่าที่ส่งไป จะเหมือนกับการส่งค่าปกติ ในตัวอย่างเราส่งข้อมูลแบบ POST เวลารับค่า
ในไฟล์ฝั่ง server เราก็รับค่าด้วยตัวแปร $_POST กรณีส่งค่าแบบ GET ก็รับค่าผ่านตัวแปร $_GET ส่วนถ้าเป็นไฟล์ส่งไป
ก็รับค่าผ่านตัวแปร $_FILSES เป็นรูปแบบการใช้งานปกติ ในที่นี้จะไม่ขอยกตัวอย่างโค้ดการอัพโหลดไฟล์ สามารถค้นหา
เพิ่มเดิมในเว็บไซต์ได้ จะแสดงแค่ค่าที่ถูกส่งไปดังนี้
ไฟล์ show_data.php
<?php header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Headers: Content-Type,X-Custom-Header'); header('Content-type: application/json'); $jsonData = array(); if($_SERVER['REQUEST_METHOD'] == "GET") { echo json_encode($jsonData); }elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS"){ echo json_encode($jsonData); }elseif($_SERVER['REQUEST_METHOD'] == "POST"){ echo json_encode(array( "statusCode"=>"200 OK", "statusMessage"=>"Successful", "getData"=>$_GET, "fileUpload"=>$_FILES, "postData"=>$_POST )); }else{ header("HTTP/1.1 403 Access Forbidden"); echo json_encode(array( "statusCode"=>"403 Access Forbidden", "statusMessage"=>"Fail" )); } ?>
ทดสอบการทำงาน กรอกข้อมูล เลือกรูปภาพ จากนั้นกด submit
จะได้ค่าของข้อมูลที่ถูกส่งไปดังนี้
สามารถนำไปประยุกต์เพิ่มเติมได้ตามต้องการ
ตอนนี้เราได้ทำความเข้าใจในการรับส่งข้อมูลระหว่าง front-end และ backend ไปมากพอสมควรแล้ว เนื้อหาต่อไป
จะเป็นอะไร รอติดตาม