การใช้งาน HttpClient ติดต่อกับ backend service ใน Angular ตอนที่ 1

เขียนเมื่อ 7 ปีก่อน โดย Ninenik Narkdee
httpclient service api angular httpclientmodule

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

ดูแล้ว 26,040 ครั้ง


เนื้อหาต่อไปนี้ เราจะมาดูในส่วนของการติดต่อรับส่งข้อมูลระหว่าง front-end และ backend service
ผ่าน HTTP protocal โดยในเนื้อหาเราจะเน้นไปส่วนของการใช้งาน front-end และสำหรับในส่วนของการจัดการ
ฝั่ง backend service หรือที่เราอาจจะคุ้นในชื่อว่า api นั้น สามารถอ่านเพิ่มเติม กรณีใช้งาน Codeigniter ได้ที่
 
หรือจะใช้ php framework อื่นจัดการเกี่ยวกับ Backend ก็ได้เหมือนกัน
ก่อนใช้งาน HttpClient ให้เราทำการติดตั้ง HttpClientModule เข้ามาใช้งานใน App ของเราก่อน ดังนี้
 

ติดตั้ง HttpClientModule ก่อนใช้งาน

    ให้เราเปิดไฟล์ app.module.ts จากนั้นให้ import HttpClientModule จาก @angular/common/http
เข้ามาใช้งาน เสร็จแล้ว import HttpClientModule เข้าไมาใช้งานใน @ngModule โดยให้กำหนดไว้หลังจาก
BrowserModule 
 
ไฟล์ app.module.ts
 
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { PagenofoundComponent } from './pagenofound/pagenofound.component';
import { HomeComponent } from './home/home.component';


@NgModule({
  declarations: [
    AppComponent,
    PagenofoundComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
 
หลังจากเราทำการ import HttpClientModule เข้ามาใช้งานใน AppModule แล้ว ก็จะสามารถ inject httpClient service
เข้าไปใช้งานใน component หรือ service อื่นๆ ใน app ของเราได้
 

 

การเรียกดูข้อมูล JSON Data ด้วยคำสั่ง GET ใน HttpClient

    รูปแบบข้อมูล json string data เป็นรูปแบบข้อมูลที่มีความนิยมในการนำมาใช้สำหรับสื่อสารระหว่าง API กับ front-end
Application ก่อนที่เราจะทดสอบการใช้งานกับ api service ฝั่ง server เราจะลองทดสอบกับไฟล์ใน app ของเราก่อน
โดยจะเป็นไฟล์นามสกุล json เริ่มต้นให้เราสร้างไฟล์ data1.json แล้วบันทึกไว้ในโฟลเดอร์ assests > data ใน project 
ของเราดังนี้
 
 


 
 
เราสร้างโฟลเดอร์ data ไว้ในโฟลเดอร์ assets แล้วสร้างไฟล์ data1.json ไว้ด้านในอีกที จำไว้เสมอว่า ในการใช้งานไฟล์ต่างๆ
เช่น ไฟล์รูปภาพ ไอคอน ฟอนท์ หรือไฟล์ข้อมูลต่างๆ เราจะเก็บไว้ที่โฟลเดอร์ assests 
 
ไฟล์ data1.json
 
{
    "results": [
      "Item 1",
      "Item 2",
      "Item 3",
      "Item 4",
      "Item 5"
    ]
  }
 
สังเกตรูปแบบข้อมูลข้างต้น จะเป็นข้อมูล json string object array ของ string 
เวลาเราอ้างอิงไฟล์สำหรับใช้งาน จะเรียกผ่าน path /assets/data/data1.json
 
ในการทดสอบการใช้งาน httpClient สำหรับเนื้อหานี้ เราจะสร้างทดสอบไว้ที่ HomeComponent โดยจะทดสอบ
ดึงข้อมูลจากไฟล์ data1.json ไปแสดงด้วยการใช้งาน httpClient ดังนี้
 
ไฟล์ home.component.html
 
<p>  home works!</p>

<ul class="list-group">
  <li class="list-group-item active">Get json string data from File </li>
  <li class="list-group-item" *ngFor="let result of results">
    {{ result }}
  </li>
</ul>
 
บรรทัดที่ 5 เราจะวนลูปข้อมูลจากตัวแปร results ที่เป็นข้อมูลแบบ string array ใน HomeComponent
ที่ได้จากการไปดึงข้อมูล json string data ในไฟล์ data1.json มาใช้งาน
 
ไฟล์ home.component.ts
 
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  // selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  public results:string[];// กำหนดตัวแปร เพื่อรับค่า

  // Inject HttpClient มาใช้ใน component หรือ service.
  constructor(private http:HttpClient) { }

  ngOnInit() {
    // ทำการเรียกใช้ HTTP request ผ่าน get() method 
    // ซึ่งจะได้ข้อมูลกลับมาในรูปแบบ Observable เราต้อง subscibe ตัว observer จึงจะทำงาน
    // พอรอค่าที่จะถูกส่งกลับมาแล้วทำงาน
    this.http.get('/assets/data/data1.json').subscribe(data => {
      // อ่านค่า result จาก JSON response ที่ส่งออกมา
      this.results = data['results'];
    });    
  }

}
 
บรรทัดที่ 3 เรา import HttpClient เข้ามา จากนั้นนำใป inject เข้ามาใช้งานใน HomeComponent ในส่วนของ constructor
ฟังก์ชั่นอ้างอิงผ่านตัวแปร http  ในบรรทัดที่ 13
บรรทัดที่ 19 ทำการเรียกใช้งานเพื่อเรียกดูข้อมูลจากไฟล์ data1.json ผ่านคำสั่ง get() ทำงานและรอรับค่าผ่านการ subscribe
เนื่องจากค่าที่ได้จาก get() จะเป็นข้อมูล Observable ย้ำอีกครั้งข้อมูล Observable คือข้อมูลที่มีการลำดับการแสดงค่าของ
ข้อมูลหรือ evnet กลับมาแบบเห็นได้ชัดเป็นลำดับขั้นตอน สามารถนำไปดำเนินการต่อได้ เมื่อ observer ทำการตรวจจับข้อมูล
ที่จะถูกส่งกลับมาอาจจะในทันที synchronous หรือส่งมาภายหลังแบบ asynchronous แล้วก็จะนำค่านั้นไปใช้งานคำสั่ง 
callback ในทันที ในตัวอย่างบรรทัดที่ 21 ก็คือนำค่าที่ได้ อ้างอิงผ่านตัวแปร data['results'] ซึ่งเป็น string array ไปเก็บ
ไว้ในัตัวแปร results ซึ่งได้กำหนดไว้รับค่าไว้ในบรรทัดที่ 10
 
ผลลัพธ์ที่ได้จะเป็นดังรูป
 
 



 
 
เราสามารถอ้างอิงค่าข้อมูลที่ส่งกลับมาในรูปแบบ json object ก็ได้ เช่น
 
this.results = data.results;
 

 
 
แต่ใน typescript อาจจะมีการแจ้งเตือนว่าไม่พบ results property ของ data object ที่ส่งกลับมา
เนื่องจากในขณะที่ HttpClient ทำการแปลงค่าจาก json string data เป็น json object ตัว typescript 
ยังไม่รู้หน้าตาของ object นั้น จึงมีแจ้งเตือน ถึงแม้ผลลัพธ์จากแสดงได้ปกติก็ตาม
 
เราสามารถแก้ปัญหากรณ้ข้างต้นโดยการกำหนดการตรวจสอบชนิดของข้อมูลที่ได้มาก่อนเรียกใช้งานได้


 

การตรวจสอบชนิดของข้อมูลก่อนเรียกใช้งาน

    กรณ้ที่เราต้องกำหนดรูปแบบข้อมูลซึ่งเราพอจะรู้อยู่แล้วว่าข้อมูลที่ส่งกลับมามีรูปแบบเป็นแบบไหน
และเราต้องการให้ตัวรับค่ารองรับรูปแบบข้อมูลนั้น สามารถใช้การกำหนด interface ให้กับ response 
ที่ส่งกลับมาได้ดังนี้
 
ไฟล์ home.component.ts
 
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// กำหนดหน้าตาของข้อมูลที่ได้รับจาก response
interface ItemsResponse{
  results:string[]
}

@Component({
  // selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  public results:string[];// กำหนดตัวแปร เพื่อรับค่า

  // Inject HttpClient มาใช้ใน component หรือ service.
  constructor(private http:HttpClient) { }

  ngOnInit() {
    // ทำการเรียกใช้ HTTP request ผ่าน get() method 
    // ซึ่งจะได้ข้อมูลกลับมาในรูปแบบ Observable เราต้อง subscibe ตัว observer จึงจะทำงาน
    // พอรอค่าที่จะถูกส่งกลับมาแล้วทำงาน
    this.http.get<ItemsResponse>('/assets/data/data1.json').subscribe(data => {
      // อ่านค่า result จาก JSON response ที่ส่งออกมา
      this.results = data.results;
      //this.results = data['results'];
    });    
  }

} 
 
สังเกตว่าตัวแจ้งเตือนในส่วนของ การเรียกใช้ data.results จะไม่ขึ้นสีแดงแล้ว ดังรูป
 
 

 
 
 
สำหรับรูปแบบของ json string data นั้น มีรูปแบบแตกต่างกัน ดังนั้นการจะกำหนด interface ก็ต้องกำหนดให้สอดคล้อง
กับรูปแบบข้อมูลนั้นด้วย สมมติให้เราสร้างไฟล์ json อีกไฟล์เข้ามาสำหรับทดสอบดังนี้
 
ไฟล์ data2.json
 
[
	{
		"id": 1,
		"name": "โคมไฟตั้งโต๊ะ",
		"detail": "รายละเอียด โคมไฟตั้งโต๊ะ",
		"price": 300,
		"quantity": 20
	},
	{
		"id": 2,
		"name": "ไฟฉายพกพา",
		"detail": "รายละเอียด ไฟฉายพกพา",
		"price": 200,
		"quantity": 50
	},
	{
		"id": 3,
		"name": "แบตเตอรี่สำรอง",
		"detail": "รายละเอียด แบตเตอรี่สำรอง",
		"price": 600,
		"quantity": 30
	}
]
 
รูปแบบของข้อมูลไฟล์ data2.json นั้นจะเป็น array ของ object และไม่มี property ชื่อ results เหมือน ไฟล์ 
data1.json ดังนั้น เราไม่ต้องกำหนด interface สำหรับกรณี้นี้ก็ได้ และสำหรับตัวรับค่า เราก็กำหนดเป็น any
 
ไฟล์ home.component.ts
 
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  // selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  public results:any;// กำหนดตัวแปร เพื่อรับค่า

  // Inject HttpClient มาใช้ใน component หรือ service.
  constructor(private http:HttpClient) { }

  ngOnInit() {
    // ทำการเรียกใช้ HTTP request ผ่าน get() method 
    // ซึ่งจะได้ข้อมูลกลับมาในรูปแบบ Observable เราต้อง subscibe ตัว observer จึงจะทำงาน
    // พอรอค่าที่จะถูกส่งกลับมาแล้วทำงาน
    this.http.get('/assets/data/data2.json').subscribe(data => {
      // อ่านค่า result จาก JSON response ที่ส่งออกมา
      this.results = data;
    });    
  }

}
 
และเนื่องจากข้อมูลที่ได้รับมาเป็นรูปแบบ array ของ object ดังนั้นในแต่ละ object เราสามารถอ้างอิง
แต่ละ property ดังนั้นให้แก้ไขการแสดงผลในไฟล์ home.component.html เป็นดังนี้
 
ไฟล์ home.component.html
 
<p>  home works!</p>

<ul class="list-group">
  <li class="list-group-item active">Get json string data from File </li>
  <li class="list-group-item" *ngFor="let result of results">
    {{ result.id }}
    {{ result.name }}
    ราคา {{ result.price }} บาท
  </li>
</ul>
 
ผลลัพธ์ที่ได้จะเป็นดังนี้
 
 
 

 

การเรียกดูค่า Response แบบเต็มรูปแบบ

    ถึงแม้ส่วนใหญ่แล้ว เราจะสนใจเฉพาะส่วนของเนื้อหาข้อมูลที่ถูกส่งกลับมา หรือที่เรียกว่าค่า response body
แต่ในบางกรณีที่เราต้องการค่าทั้งหมดของ response ที่ส่งกลับมา ซึ่งรวมไปถึง response header ด้วย ทั้งนี้
ก็อาจจะเพื่อใช้ในการกำหนดเงื่อนอื่นเพิ่มเติม เช่น ต้องการดูค่า header ที่มีการกำหนดเพื่มเติมส่งกลับมา
หรือต้องการดู status code เช่น 200 หรือ 404 เป็นต้น เพื่อให้ใช้คุณสมบัตินี้ เราต้องกำหนด option เพิ่มเติมใน
ขั้นตอนการเรียกดูข้อมูลผ่านคำสั่ง get() ดังนี้
 
ไฟล์ home.component.ts บางส่วน
 
export class HomeComponent implements OnInit {
  public results:any;// กำหนดตัวแปร เพื่อรับค่า
  public headerDatas:any;
  public jsonData:any;

  // Inject HttpClient มาใช้ใน component หรือ service.
  constructor(private http:HttpClient) { }

  ngOnInit() {
    // ทำการเรียกใช้ HTTP request ผ่าน get() method 
    // ซึ่งจะได้ข้อมูลกลับมาในรูปแบบ Observable เราต้อง subscibe ตัว observer จึงจะทำงาน
    // พอรอค่าที่จะถูกส่งกลับมาแล้วทำงาน
    this.http.get('/assets/data/data2.json', {observe: 'response'})
    .subscribe(resp => {
      console.log(resp);// ดูโครงสร้างของ json ทั้งหมดผ่าน console
      console.log(resp.status); // ดู status code ได้ค่า 200
      this.jsonData = resp;
      // this.headerDatas = resp.headers.get('ชื่อ property ส่วน header ที่ต้องการ');
      // ใช้ข้อมูลในส่วนของ response body  ที่ส่งออกมา
      this.results = resp.body;
    });    
  }

}
 
แสดงข้อมูล response ทั้งหมด ในไฟล์ home.component.html
 
ไฟล์ home.component.html
 
<p>  home works!</p>

<ul class="list-group">
  <li class="list-group-item active">Get json string data from File </li>
  <li class="list-group-item" *ngFor="let result of results">
    {{ result.id }}
    {{ result.name }}
    ราคา {{ result.price }} บาท
  </li>
</ul>
<pre>
  {{ jsonData | json }}
</pre>
 
ตัวอย่างผลลัพธ์บางส่วน
 

 
 
 

การจัดการ Error จากการเรียกดูข้อมูล

    ในกรณีที่มีข้อผิดพลาดหรือ Error เกิดขึ้นจากการเรียกดูข้อมูลผ่าน server เช่นไม่พบไฟล์ หรือ 
การเชื่อมต่อไม่เสถียร เหล่านี้เป็นต้น ตัว HttpClient จะ return ค่ากลับมาเป็น error โดยค่าที่ return
กลับมาซึ่งเป็น Observable ก็มีตัวจัดการ error นี้ผ่าน error callback function โดยจะคืนค่าข้อความ
หรือ error object ดังนี้
 
    this.http.get('/assets/data/data2.json')
    .subscribe(
      data => {
        // กรณี resuponse success
        this.results = data;
      },
      err => {
        // กรณี error
        console.log('Something went wrong!');
      }
    );     
 

 

การดูรายละเอียดข้อมูลของ Error

    จากรูปแบบการตรวจสอบ error ข้างต้น หากเราต้องการจะทราบถึงรายละเอียดของ error
ที่เกิดขึ้น ว่าเกิดจากอะไร เราสามารถใช้ค่าจาก err parameter ซึ่งเป็นค่า ของข้อมูล HttpErrorResponse 
ที่ส่งเข้าไปใน callback มาใช้งานได้ ดังนี้
 
    this.http.get('/assets/data/data2.json')
    .subscribe(
      data => {
        // กรณี resuponse success
        this.results = data;
      },
      ( 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}`);
        }       
      }
    );        
  }
 
โดยกรณีมีการใช้งาน HttpErrorResponse เราต้องทำการ import HttpErrorResponse เข้ามาใช้งาน ใน
component ด้วย 
 
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
 

การพยายามเรียกดูข้อมูลซ้ำด้วยคำสั่ง retry() 

    เนื่องจากข้อมูลที่ได้จาก HttpClient เป็นข้อมูลแบบ Observable ดังนั้น กรณีเกิด error ขึ้น เช่นในขณะเรียกดู
ข้อมูลครั้งแรกในตอนนั้นการเชื่อมต่อข้อมูลไม่เสถียร ก็จะมีการส่ง err กลับมาทำงานในส่วนของ error callback
และก็จบการทำงานไป แต่ถ้าเราต้องการให้ทำงานซ้ำ โดยใช้ retry ซึ่งเป็น operator หรือตัวดำเนินการหรือจัดการ
กับข้อมูล Observable เข้ามาใช้งาน เราก็จะสามารถให้ทำซ้ำ การเรียกดูข้อมูลตามจำนวนครั้งทีเรากำหนด ซึ่งถ้าทำ
ซ้ำจนครบจำนวนครั้งที่กำหนดแล้ว ก็ยังไม่สำเร็จก็จะส่ง error กลับมาและจบการทำงานไป 
    ตัวอย่างเข่น สมมติเรากำหนดให้ทำซ้ำ 3 ครั้งด้วยคำสั่ง retry(3) หากครั้งแรกยังไม่สำเร็จ ก็จะเรียกข้อมูลครั้งที่ 2
หากครั้ง 2 สำเร็จ ก็จะไม่ทำครั่งที่ 3 แต่จะคืนค่ากลับมาและทำงานใน success callback และจบการทำงาน
 
    this.http.get('/assets/data/data2-.json')
    .retry(3) // ทำการ subscribe ซ้ำได้ไม่เกิน 3 ครั้ง
    .subscribe(
      data => {
        // กรณี resuponse success
        this.results = data;
      },
      ( 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}`);
        }       
      }
    );   
 
โดยในการใช้งาน retry operator ของ Observable เราต้อง import เข้ามาใช้งานด้งนี้ด้วย
 
import 'rxjs/add/operator/retry';
 
การใช้ retry(3) นั้นหมายถึง จะมีการ subscribe ทั้งหมด 4 ครั้งหาก ผลลัพธ์สุดท้ายแล้วเป็น error ซึ่งก็คือ
เมื่อทำครั้งแรกจะเป็น subscribe ปกติ นับเป็น 1 และให้ทำ retry อีก 3 ครั้ง รวมเป็น 4 โดยถ้า retry ในครั้ง
ที่ใดๆ แล้ว response success กลับ ก็จะไม่มีการ retry ต่อ
 
 

การเรียกดูข้อมูลที่ไม่ใช่ JSON String Data

    นอกจากไฟล์หรือข้อมูลที่เป็น json string data แล้ว HttpClient ยังสามารถรองรับการเรียกดูข้อมูลอื่น
ได้ โดยการกำหนดค่า responseType ให้กับข้อมูลที่ return กลับมา
    ให้เราทดสอบสร้างไฟล์ mytext.txt เพื่อทดสอบเรียกดูข้อมูลที่ไม่ใช่ json string data กันดู
 
ไฟล์ mytext.txt
 
ข้อความของไฟล์ mytext.txt 
ข้อความของไฟล์ mytext.txt 
ข้อความของไฟล์ mytext.txt
 
ส่วนของการเรียกใช้งาน
 
ไฟล์ home.component.ts บางส่วน
 
export class HomeComponent implements OnInit {
  public results:any;// กำหนดตัวแปร เพื่อรับค่า
  public headerDatas:any;
  public jsonData:any;
  public textData:string;

  // Inject HttpClient มาใช้ใน component หรือ service.
  constructor(private http:HttpClient) { }

  ngOnInit() {
    // ทำการเรียกใช้ HTTP request ผ่าน get() method 
    // ซึ่งจะได้ข้อมูลกลับมาในรูปแบบ Observable เราต้อง subscibe ตัว observer จึงจะทำงาน
    // พอรอค่าที่จะถูกส่งกลับมาแล้วทำงาน

    this.http.get('/assets/data/mytext.txt', {responseType: 'text')
    .retry(3) // ทำการ subscribe ซ้ำได้ไม่เกิน 3 ครั้ง
    .subscribe(
      data => {
        // กรณี resuponse success
        this.textData = data;
      },
      ( 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}`);
        }       
      }
    );        
  }

}
 
ไฟล์ home.component.html บางส่วน
 
<pre>
  {{ textData }}
</pre>
 
ผลลัพธ์ที่ได้
 
 


 
 
โดยการกำหนด responseType option ที่เพิ่มเข้าไปนั้น 
 
{responseType: 'text'}
 
สามารถกำหนดค่าได้เป็น 'arraybuffer' | 'blob' | 'json' | 'text' โดยถ้าไม่ได้กำหนดส่วนนี้ ค่าเริ่มต้นจะเป็น json 
 
เนื้อหาตอนหน้าเราจะมาดูการทำ workshop การแสดงข้อมูลด้วย HttpClient ผ่าน get() method 
รวมถึงการแบ่งหน้าข้อมูลด้วย รอติดตาม

 


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



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









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









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





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







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