การใช้งาน Progress Event กับการอัพโหลดไฟล์ ใน HttpClient ตอนที่ 6

เขียนเมื่อ 7 ปีก่อน โดย Ninenik Narkdee
progress event httpclient angular

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ progress event httpclient angular

ดูแล้ว 7,886 ครั้ง


เนื้อหาในตอนที่ 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
 
1
2
3
#place_previewImg{
    margin-bottom: 10px;
}
ไฟล์ register.component.html
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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 สำหรับอัพโหลไฟล์รุปภาพ 
 
1
<input (change)="onChangePic($event)" type="file" name="file2" id="file2" class="custom-file-input" accept="image/*">
 
มีการกำหนด onChange event เข้าไปสำหรับทำการแสดงรูปภาพ preview ผ่านฟังก์ชั่น onChangePic($event) 
โดยส่งค่า Event Object เข้าไปในฟังก์ชั่นด้วย เมื่อมีการเลือกไฟล์รูปภาพ ก็จะให้แสดงชื่อไฟล์รูปภาพผ่านตัวแปร 
chooseFile
 
1
<span class="custom-file-control">{{ chooseFile }}</span>
 
และแสดงรูป prview ในส่วนของแท็ก img
 
1
<img id="place_previewImg" *ngIf="previewLoaded" [src]="previewImg" style="height:150px;">
 
หน้าตาของส่วนของฟอร์มอัพโหลดไฟล์ จะได้เป็นดังนี้
 
 


 
 
ตัวอย่างเมื่อเลือกไฟล์ (กรณีเพิ่มโค้ด javascript แล้ว)
 
 


 
 
ต่อไปดูในส่วนของไฟล์การทำงานเพื่อทำการอัพโหลดไฟล์ให้เราปรับไฟล์ register.component.ts เป็นดังนี้
 
ไฟล์ register.component.ts
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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();
    $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
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<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
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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 ไปมากพอสมควรแล้ว เนื้อหาต่อไป 
จะเป็นอะไร รอติดตาม 
 


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ