การใช้งาน Refresher และ Infinite Scroll จัดการรายการข้อมูลใน Ionic

เขียนเมื่อ 6 ปีก่อน โดย Ninenik Narkdee
ionic native infinite scroll refresher

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ ionic native infinite scroll refresher

ดูแล้ว 6,356 ครั้ง


สำหรับเนื้อหาในตอนต่อไปนี้ จะเป็นการเพิ่มลูกเล่น ต่อยอดจากตอนที่แล้ว เกี่ยวกับการจัดการข้อมูล
ในลิสรายการ ซึ่งสามารถทำความเข้าใจและประยุกต์กับการใช้งานส่วนอื่นๆ ได้ ทบทวนบทความ
ในตอนที่แล้วได้ที่
    ใช้งาน Popover สร้างเงื่อนไข การดึงข้อมูลด้วย Provider ใน ionic http://niik.in/864 

 

การใช้งาน Refresher

    ส่วนแรกที่เราจะพูดถึงคือการใช้งาน Refresher module ผ่าน <ion-refresher> component ซึ่งวิธีการเรียกใช้งาน
นั้นจะง่าย ไม่ซับซ้อนอะไร ลักษณะหรือรูปแบบการใช้งาน เป็นลักษณะการ refresh ข้อมูลโดยการ กดค้างที่เนื้อหาลิส
รายการข้อมูล แล้วดึงลงมา หรือที่เราอาจจะคุ้นในคำว่า 'Pull to refresh' เมื่อเราปล่อย ก็จะทำการไปเรียกคำสั่งดึงข้อมูล
มาใหม่ หรือไปเรียกข้อมูลมาใหม่เสมือนกับการ refresh ข้อมูล หน้าตาขณะเรากดและดึงลิสรายการลอง จะแสดงตาม
รูปด้านล่าง โดยเมื่อเราปล่อย และในระหว่างไปดึงข้อมูล ก็จะแสดง loading และเมื่อมีการส่งข้อมูลกลับมา ตัว loading
ก็จะหายไป
 
 


 
 
เริ่มต้นให้เราเพิ่มการใช้งาน <ion-refresher> เข้าไปในไฟล์ province.ts ตามโค้ดส่วน highlight ดังนี้
 
ไฟล์ province.ts
 
<ion-header>
  <ion-navbar color="primary">
      <button ion-button menuToggle>
          <ion-icon name="menu"></ion-icon>
        </button>   
  <ion-title>province</ion-title>
  <ion-buttons end>
      <button ion-button icon-only (click)="openFilter($event)">
        <ion-icon name="funnel"></ion-icon>
      </button>
    </ion-buttons>    
</ion-navbar>
<ion-toolbar no-border-top>
    <ion-searchbar color="primary"
                   [(ngModel)]="queryText"
                   (ionInput)="getItems()"
                   placeholder="Search">
    </ion-searchbar>
  </ion-toolbar>  
</ion-header>

<ion-content>
  <ion-refresher (ionRefresh)="doRefresh($event)">
    <ion-refresher-content></ion-refresher-content>
  </ion-refresher>  
  <ion-list>
    <ion-item  *ngFor="let province of results;let i=index" >
    {{province.name}}
    </ion-item>
  </ion-list>
</ion-content>
 
ในตัว refresher จะมี ionRefresh event ที่เมื่อเรากดและดึงรายการลงมา แล้วให้ทำคำสั่ง doRefresh($event) ซึ่งเป็น
ฟังก์ชั่นคำสั่งที่เรากำหนดขึ้น โดยส่ง $event object เข้าไปด้วย $event นี้จะเป็น Refresher event ที่ใช้สำหรับจัดการ
เกี่ยวกับการใช้งาน refresher component อีกที จะเห็นว่า มีการใช้งานที่ง่าย ไม่ซับซ้อน โค้ดที่เพิ่มเข้ามาแค่ 3 บรรทัด
 
ต่อไปเป็นส่วนของโค้ดตัวจัดการในไฟล์ province.ts ให้เราปรับโค้ดเป็นดังนี้
 
ไฟล์ province.ts
 
import { Component } from '@angular/core';
import { 
  NavController, 
  NavParams, 
  PopoverController,
  ToastController,
  Refresher
} from 'ionic-angular';

// มีการใช้งาน PopoverController
// import service และ popover page มาใช้งาน
import { ProvinceServiceProvider } from '../../providers/province-service/province-service';
import { PopoverPage } from '../popover/popover';
 
@Component({
  selector: 'page-province',
  templateUrl: 'province.html',
})
export class ProvincePage {
 
    // กำหนดตัวแปร สำหรับค่าต่างๆ 
  public queryText:string = ''; // เก็บข้อความที่ใช้ค้น
  public results:any; // ไว้รับข้อมูล
  public sorttype:string = 'province_id ASC'; // เก็บรุปแบบเงื่อนไขการเรียงข้อมูล
 
  constructor(
    public navCtrl: NavController, 
    public navParams: NavParams,
    public popoverCtrl: PopoverController, // inject PopoverController มาใช้งาน
    public provinceService:ProvinceServiceProvider, // inject ProvinceServiceProvider มาใช้งาน
    public toastCtrl: ToastController
  ) {
  }
 
  ionViewDidLoad() {
    console.log('ionViewDidLoad ProvincePage');
    // เมื่อเข้ามาใน province page ให้ทำการดึงข้อมูลมาแสดงทันที
    this.provinceService.getProvince().subscribe((data) =>{
      this.results = data;
    });
   
 
  }
 
  // ฟังก์ชั่นคำสั่งสำหรับแสดง popover page มีการรับค่า $event object ผ่านตัวแปร ev
  openFilter(ev:any){
  // กำหนด option ให้กับ popover ที่จะสร้าง ในที่นี้ส่ง cssClass 
  // เข้าไปเพื่อกำหนดพื้นหลังสีทิบมี popover แสดง
    let popoverOpt = {
      cssClass: 'backdropOpacityPopover'     
    }
    // กำหนดตัวแปร ที่จะส่งไปยัง popover ในที่นี้เราส่งค่าเริ่มต้นการจัดเรียงข้อมูลไป
    let popoverData = {
      sorttype:this.sorttype
    }
    // กำหนดตัวแปรสร้าง popover มีส่งค่า และกำหนด option 
    let popover = this.popoverCtrl.create(PopoverPage,popoverData,popoverOpt);
    popover.present({
  //    ev: ev // แสดง popover และส่งค่า $event object ผ่านตัวแปร ev เข้าไปด้วย
    }); 
    // การส่ง $event object เข้าไปใน popover จะทำให้สามารถอ้างอิงตำแหน่งของปุ่ม ทำให้ popover แสดง
    // ที่ตำแหน่งปุ่มบน ในตัวอย่าง เราจะ comment  // ev:ev เพื่อให้ popover แสดงตรงกลางแทน
     
    // กำหนด event เมื่อ popover ปิดลง ให้รับค่า รูปแบบการจัดเรียง ถ้ามีการส่งค่ากลับมา
    popover.onDidDismiss((data)=>{
      if(data){
        this.sorttype = data.sorttype;
      }
      this.getItems(); // ทำการดึงข้อมูล
    });
  } 
 
  // ฟังก์ชั่นดึงข้อมูล โดยเรียกใช้ผ่าน provinceService อีกที
  getItems(){
    let q = this.queryText;
    let sort = this.sorttype;
    this.provinceService.getProvince(q,sort)
    .subscribe((data) =>{
      this.results = data;
    });
  }

  doRefresh(refresher: Refresher) {
    let q = this.queryText;
    let sort = this.sorttype;    
    this.provinceService.getProvince(q,sort).subscribe((data) =>{
      this.results = data;
      refresher.complete();
   
      let toast = this.toastCtrl.create({
        message: 'แสดงรายการอัพเดทล่าสุดเรียบร้อยแล้ว.',
        duration: 3000
      });
      toast.present();      
    });
  }
 
}
 
ในโค้ดตัวอย่างข้างต้น เรามีการใช้งาน ToastController และ Refresher โดย import เข้ามาใช้งานในบรรทัดที่ 6 และ 7
จากนั้น inject ToastController เข้ามาใน page ตามบรรทัดที่ 31
    Toast คือรูปแบบการแสดงข้อความแจ้งเตือน โดยจะแสดงอยู่เหนือรายการอื่นๆ คล้าย popup  เราจะใช้ toast นี้แสดงข้อ
ความแจ้ง หลังจากทำการดึงข้อมูลใหม่แล้วว่า  'แสดงรายการอัพเดทล่าสุดเรียบร้อยแล้ว.' เพื่อแจ้งให้ผู้ใช้ทราบการดำเนินการ
ผ่าน Refresher ได้สำเร็จเรียบร้อยแล้ว
     ส่วนของโค้ดที่เราเพิ่มเข้ามาคือฟังก์ชั่น doRefresh() ในคำสั่งนี้ เราจะมี Refresher ซึ่งเป็น observer เข้ามาในฟังก์ชั่น
ตัว observer นี้จะเป็นเหมือนตัวที่คอยตรวจสอบการทำงาน คือเมื่อเราทำการ refresh ข้อมูล ตัวแสดงการ loading ก็จะแสดง
จนกว่าจะมีการสั่งให้หยุดแสดง ดังนั้น เมื่อทำการไปดึงข้อมูลผ่าน this.provinceService.getProvince() เรียบร้อย และส่งข้อมูล
กลับมาแล้ว เราจะสั่ง  Refresher.complete() เพื่อให้ส่วนของ loading ปิดไป  
    หลังจากโหลดข้อมูลใหม่สำเร็จ และปิดตัว loading ไปแล้ว เราก็ให้ toast ทำการแสดงข้อความแจ้งเตือน 3 วินาที ก่อนปิด
การแจ้งเตือนไป
 
ตัวอย่างผลลัพธ์การแจ้งเตือนเมื่อทำการ refresh ข้อมุลเรียบร้อยแล้ว
 
 


 
 
    สามารถดูการตั้งค่าเพิ่มเติม เกี่ยวกับการใช้งาน refresher และ toast ได้ที่
 
 


 

การใช้งาน Infinite Scroll

    ต่อไปเราจะประยุกต์การใช้งาน infinite scroll ซึ่งจะใช้กับลิสรายการข้อมูลที่มีจำนวนมากๆ ไม่เหมาะที่จะดึงมาแสดงทั้งหมด
ในครั้งเดียว ลักษณะการทำงานก็คล้ายๆ กับการแบ่งเป็นหน้าๆ แต่ไม่ใช้วิธีการเลือกหน้าข้อมูล แต่เป็นการเลื่อนไปยังตำแหน่ง
ด้านล่าง หรือการ scroll ไปยังด้านล่าง ซึ่งค่าเริ่มต้นจะอยู่ที่ 150px จากด้านล่าง โดยเมื่อเลื่อนไปถึงตำแหน่งดังกล่าว ก็จะให้ไป
ทำคำสั่งโหลดข้อมูลหน้าถัดไปมาแสดงต่อข้อมูลไปเรื่อยๆ จนแสดงข้อมูลครบ
    ดังนั้นการจะใช้งาน infinite scroll เราจำเป็นจะต้องจัดการในส่วนของ service api ให้รองรับการแสดงข้อมูลแบบแบ่งหน้าได้
รวมถึงมีข้อมูลบางค่าที่ต้องใช้งานเพิ่มเติมเข้ามา ด้านล่างคือรูปแบบ json data ของข้อมูล บางส่วน ที่เราจะใช้

{
	"curPage": 1,
	"perPage": 15,
	"totalData": 15,
	"totalPage": 6,
	"data": [
		{
			"id": "1",
			"name": "กรุงเทพมหานคร",
			"geo_id": "2"
		},
		{
			"id": "2",
			"name": "สมุทรปราการ",
			"geo_id": "2"
		}
	]
}
 
จะเห็นว่า จากแต่เดิม เราจะใช้ json data เป็นในรูปแบบ array object ของข้อมูล หรือก็คือเฉพาะในส่วนของ data เท่านั้น แต่กรณี
นี้ เราจะมีค่า 
  • curPage หน้าปัจจุบัน 
  • perPage จำนวนที่แสดงสูงสุดในแต่ละหน้า 
  • totalData จำนวนข้อมูลที่มีข้อมูลแสดงในแต่ละหน้า เช่น หน้าสุดท้าย อาจจะมีไม่ถึง 15 รายการก็ได้
  • totalPage จำนวนหน้าทั้งหมด ได้จากการคำนวณจำนวนทั้งหมด หาร จำนวนที่แสดงแต่ละหน้า ถ้ามีเศษปัดขึ้น
  • data array object ของข้อมูล
 
เพื่อให้ได้รูปแบบ api ข้างต้น ให้เราทำการจัดรูปแบบไฟล์ api.php สำหรับใช้ในการทดสอบใหม่เป็นดังนี้
 
ไฟล์ api.php
 
<?php
header('Access-Control-Allow-Origin: *'); 
header("Content-type:application/json; charset=UTF-8");    
header("Cache-Control: no-store, no-cache, must-revalidate");         
header("Cache-Control: post-check=0, pre-check=0", false); 
require_once("dbconnect.php");

$perPage = 15;
$totalData = 0;
$totalShow = 0;
$totalPage = 0;

if(!isset($_GET['page']) || $_GET['page']<=0){
	$page = 1;
	$beginPage = 0;
}else{
	$page =  (int) $_GET['page'];	
	$beginPage = ($page-1)*$perPage;
}


$json_data = array();
$more_sql = "";
// ถ้ามีการส่งคำค้นหามา และไม่ใช่ค่าว่าง
if(isset($_GET['queryString']) && $_GET['queryString']!=""){
    $more_sql = " AND province_name LIKE '%".trim($_GET['queryString'])."%' ";
}
$sort = "";
// ถ้ามีการส่งค่าเงื่อนไขการเรียงข้อมูลมา และไม่ใช่ค่าว่าง
if(isset($_GET['sorttype']) && $_GET['sorttype']!=""){
    $sort = " ORDER BY ".$_GET['sorttype']." ";
}
$sql = "
SELECT * FROM tbl_provinces WHERE 1 $more_sql $sort
";
$result = $mysqli->query($sql);
if($result && $result->num_rows > 0){
	$totalData = $result->num_rows;
}
$sql.="
 LIMIT $beginPage,$perPage
";
$result = $mysqli->query($sql);
if($result && $result->num_rows > 0){
	$totalShow = $result->num_rows;
	$json_data['curPage']=$page;
	$json_data['perPage']=15;
	$json_data['totalData']=$totalShow;
	$json_data['totalPage']=ceil($totalData/$perPage);
    while($row = $result->fetch_assoc()){
        $json_data['data'][] = array(
            "id" => $row['province_id'],
            "name" => $row['province_name'],
            "geo_id" => $row['geo_id']                
        );
    }
}
// แปลง array เป็นรูปแบบ json string  
if(isset($json_data)){  
    $json= json_encode($json_data);    
    if(isset($_GET['callback']) && $_GET['callback']!=""){    
    echo $_GET['callback']."(".$json.");";        
    }else{    
    echo $json;    
    }    
}
?>
 
โค้ดข้างต้น เรามีการเพิ่ม parameter เข้ามาใช้งาน นั่นก็คือ page ถ้าไม่มีค่าส่งเข้ามา ก็จะเป็น page แรก หรือข้อมูล
ของหน้าแรก ถ้ามีค่าส่งเข้ามา ก็จะนำไปคำนวณ เพื่มเข้าไปในคำสั่ง sql ส่วนนี้ ถ้าใครเคยใช้งานกาาแบ่งหน้าใน php 
ก็น่าจะพอทำความเข้าไจได้ไม่ยาก
 
ต่อไปเราก็ต้องไปปรับในส่วนของไฟล์ provider service โดยต้องมีการเพิ่ม parameter เข้าไปในฟังก์ชั่นเพิ่มเติม
ในไฟล์ province-service.ts ให้เราปรับโค้ดเป็นดังนี้
 
ไฟล์ province-service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
 
@Injectable()
export class ProvinceServiceProvider {
 
  constructor(public http: HttpClient) {
    console.log('Hello ProvinceServiceProvider Provider');
  }
 
  getProvince(q?:any,sort?:any,page?:any){
    return this.http.get('http://localhost/demo/api.php',{
      params: {
        queryString: q!=null?q:'',
        sorttype: sort!=null?sort:'',
        page: page!=null?page:1
      }
    }
    );   
  }
  
}
 
จากโค้ดจะเห็นว่าเรามีการเพิ่ม parameter page เข้ามาแบบ option คือมีหรือไม่มีก็ได้ ในบรรทัดที่ 11 และเรามีการส่งค่าตัวแปร
แบบ GET ไปยัง api เพิ่มเข้าไปในบรรทัดที่ 16 
 
 
ต่อไปเป็นส่วนของการใช้งานในหน้า view เราจะมีการเรียกใช้ <ion-infinite-scroll> โดยเพิ่มเข้าไปต่อด้านท้ายของลิสรายการ
ในไฟล์ province.html ดังนี้
 
ไฟล์ province.html
 
<ion-header>
  <ion-navbar color="primary">
      <button ion-button menuToggle>
          <ion-icon name="menu"></ion-icon>
        </button>   
  <ion-title>province</ion-title>
  <ion-buttons end>
      <button ion-button icon-only (click)="openFilter($event)">
        <ion-icon name="funnel"></ion-icon>
      </button>
    </ion-buttons>    
</ion-navbar>
<ion-toolbar no-border-top>
    <ion-searchbar color="primary"
                   [(ngModel)]="queryText"
                   (ionInput)="getItems()"
                   placeholder="Search">
    </ion-searchbar>
  </ion-toolbar>  
</ion-header>

<ion-content>
  <ion-refresher (ionRefresh)="doRefresh($event)">
    <ion-refresher-content></ion-refresher-content>
  </ion-refresher>  
  <ion-list>
    <ion-item  *ngFor="let province of results;let i=index" >
      {{province.name}}
    </ion-item>
  </ion-list>

  <ion-infinite-scroll (ionInfinite)="doInfinite($event)" *ngIf="page < totalPage">
    <ion-infinite-scroll-content loadingSpinner="bubbles" 
    loadingText="กำลังโหลดข้อมูล...">
    </ion-infinite-scroll-content>
  </ion-infinite-scroll>  
  
</ion-content>
 
ส่วนที่ highlight คือที่เราเพิ่มเข้ามา มีการใช้งาน ionInfinite event หรือก็คือเมื่อเราเลื่อน scroll ลงไปด้านล่างในตำแหน่งหนึ่ง
ซึ่งหากไม่กำหนดค่า ก็จะใช้ที่ 150px จากขอบด้านล่าง ก็จะไปเรียกคำสั่ง doInfinite($event) โดยส่งค่า $event  object เข้าไป
ในลักษณะคล้ายๆ กับกรณีที่เรา refresher คือเมื่อเราเลื่อนลงมาที่ตำแหน่งด้านล่าง ก็จะแสดงข้อความ พร้อม loading แจ้งว่า
กำลังโหลดข้อมูลเพิ่มเติม นอกจากนั้น เรายังกำหนดเงื่อนไข การแสดง <ion-infinite-scroll> เมื่อค่า page น้อยกว่าค่าจำนวนหน้า
ทั้งหมด นั่นก็คือ ถ้ายังไม่ใช่หน้าสุด้ทาย ก็ให้ใช้งาน <ion-infinite-scroll> แต่ถ้าเป็นหน้าสุดท้ายแล้ว ก็ไม่ต้องแสดง
    สามารถดูการตั้งค่าเพิ่มเติมเกี่ยวกับ infinite scroll ได้ที่
 
ส่วนสุดท้าย และสำคัญ ก็คือส่วนของการควบคุมข้อมูล เพื่อให้รองรับการใช้งาน infinite scroll โค้ดจะอธิายไว้ด้านล่าง
ของโค้ด ให้เราปรับไฟล์ province.ts เป็นดังนี้
 
ไฟล์ province.ts
 
import { Component } from '@angular/core';
import { 
  NavController, 
  NavParams, 
  PopoverController,
  ToastController,
  Refresher
} from 'ionic-angular';
// มีการใช้งาน PopoverController
// import service และ popover page มาใช้งาน
import { ProvinceServiceProvider } from '../../providers/province-service/province-service';
import { PopoverPage } from '../popover/popover';
 
interface resultData{
  curPage:number,
  perPage:number,
  totalData:number,
  totalPage:number,
  data:any[]
}

@Component({
  selector: 'page-province',
  templateUrl: 'province.html',
})
export class ProvincePage {
 
    // กำหนดตัวแปร สำหรับค่าต่างๆ 
  public queryText:string = ''; // เก็บข้อความที่ใช้ค้น
  public results:any; // ไว้รับข้อมูล
  public sorttype:string = 'province_id ASC'; // เก็บรุปแบบเงื่อนไขการเรียงข้อมูล
  public page:number = 1;
  public totalData:number = 0;
  public totalPage:number = 0;
 
  constructor(
    public navCtrl: NavController, 
    public navParams: NavParams,
    public popoverCtrl: PopoverController, // inject PopoverController มาใช้งาน
    public provinceService:ProvinceServiceProvider, // inject ProvinceServiceProvider มาใช้งาน
    public toastCtrl: ToastController
  ) {
  }
 
  ionViewDidLoad() {
    console.log('ionViewDidLoad ProvincePage');  
    // เมื่อเข้ามาใน province page ให้ทำการดึงข้อมูลมาแสดงทันที
    this.provinceService.getProvince().subscribe((result:resultData) =>{
      this.page = result.curPage;
      this.totalData = result.totalData;
      this.totalPage = result.totalPage;
      this.results = result.data;
    });     
  }
 
  // ฟังก์ชั่นคำสั่งสำหรับแสดง popover page มีการรับค่า $event object ผ่านตัวแปร ev
  openFilter(ev:any){
  // กำหนด option ให้กับ popover ที่จะสร้าง ในที่นี้ส่ง cssClass 
  // เข้าไปเพื่อกำหนดพื้นหลังสีทิบมี popover แสดง
    let popoverOpt = {
      cssClass: 'backdropOpacityPopover'     
    }
    // กำหนดตัวแปร ที่จะส่งไปยัง popover ในที่นี้เราส่งค่าเริ่มต้นการจัดเรียงข้อมูลไป
    let popoverData = {
      sorttype:this.sorttype
    }
    // กำหนดตัวแปรสร้าง popover มีส่งค่า และกำหนด option 
    let popover = this.popoverCtrl.create(PopoverPage,popoverData,popoverOpt);
    popover.present({
  //    ev: ev // แสดง popover และส่งค่า $event object ผ่านตัวแปร ev เข้าไปด้วย
    }); 
    // การส่ง $event object เข้าไปใน popover จะทำให้สามารถอ้างอิงตำแหน่งของปุ่ม ทำให้ popover แสดง
    // ที่ตำแหน่งปุ่มบน ในตัวอย่าง เราจะ comment  // ev:ev เพื่อให้ popover แสดงตรงกลางแทน
     
    // กำหนด event เมื่อ popover ปิดลง ให้รับค่า รูปแบบการจัดเรียง ถ้ามีการส่งค่ากลับมา
    popover.onDidDismiss((data)=>{
      if(data){
        this.sorttype = data.sorttype;
      }
      this.getItems(); // ทำการดึงข้อมูล
    });
  } 
 
  // ฟังก์ชั่นดึงข้อมูล โดยเรียกใช้ผ่าน provinceService อีกที
  getItems(){
    this.page = 1;
    let q = this.queryText;
    let sort = this.sorttype;
    let page = this.page;
    this.provinceService.getProvince(q,sort,page)
    .subscribe((result:resultData) =>{
      this.page = result.curPage;
      this.totalData = result.totalData;
      this.totalPage = result.totalPage;
      this.results = result.data;
    });
  }

  doRefresh(refresher: Refresher) {
    this.page = 1;
    let q = this.queryText;
    let sort = this.sorttype;
    let page = this.page;
    this.provinceService.getProvince(q,sort,page)
    .subscribe((result:resultData) =>{
      this.page = result.curPage;
      this.totalData = result.totalData;
      this.totalPage = result.totalPage;
      this.results = result.data;
      refresher.complete();
   
      let toast = this.toastCtrl.create({
        message: 'แสดงรายการอัพเดทล่าสุดเรียบร้อยแล้ว.',
        duration: 3000
      });
      toast.present();      
    });
  }

  doInfinite(infiniteScroll) {
    this.page = this.page+1;
    let q = this.queryText;
    let sort = this.sorttype;
    let page = this.page;
    this.provinceService.getProvince(q,sort,page)
    .subscribe((result:resultData) =>{
      this.page = result.curPage;
      this.totalData = result.totalData;
      this.totalPage = result.totalPage;
      for(let i=0; i<this.totalData; i++) { 
        this.results.push(result.data[i]);
      }      
      infiniteScroll.complete();
      
    });    

  }
 
}
 
บรรทัดที่ 14 - 20 เราต้องทำการกำหนดรูปแบบของข้อมูลที่ได้รับจาก api เพื่อให้สามารถนำไปเรียกใช้งาน
กรณีส่งค่ากลับมาแล้ว 
 
interface resultData{
  curPage:number,  // รับค่าหน้าปัจจุบันที่กำลังแสดงอยู่
  perPage:number, // รับค่าจำนวนรายการสูงสุดที่แสดงแต่ละหน้า
  totalData:number, // รับค่าจำนวนรายการของหน้านั้น มีค่า <= perPage
  totalPage:number, // รับค่าจำนวนหน้า page ทั้งหมด ได้จากการคำนวณ
  data:any[] // array object ข้อมูล
}
 
resultData นี้เป็น interface ที่เราจะใช้กำหนดให้กับผลลัพธ์ของข้อมูลที่ส่งกลับมา
 
ต่อไปเป็นส่วนของการกำหนดค่าเริ่มต้น บรรทัดที่ 29 - 34 โดยเฉพาะบรรทัดที่ 32 - 34 เป็นการสร้าง
ตัวแปรเพื่อกำหนดค่า และรับค่าที่จะใช้งาน ตัวแปร page เท่ากับ 1 คือหน้าปัจจุบันที่เปิดมาให้เป็นหน้าแรก
ส่วน totalData จะไว้เก็บรายการของหน้านั้นๆ ว่ามีกี่รายการซึ่ง ค่ามากสุดจะไม่เกิด perPage ที่กำหนดใน 
service api ส่วนค่าสุดท้าย totalPage คือค่าจำนวนหน้า page ทั้งหมด ค่าเริ่มต้นให้เท่ากับ 0 ก่อน แต่เมื่อ
ดึงข้อมูลมา ก็จะนำมากำหนดค่าใหม่
 
    // กำหนดตัวแปร สำหรับค่าต่างๆ 
  public queryText:string = ''; // เก็บข้อความที่ใช้ค้น
  public results:any; // ไว้รับข้อมูล
  public sorttype:string = 'province_id ASC'; // เก็บรุปแบบเงื่อนไขการเรียงข้อมูล
  public page:number = 1;
  public totalData:number = 0;
  public totalPage:number = 0;
 
บรรทดัที่ 48 - 53 เราจะมีการจัดรูปแบบ และกำหนดค่าให้กับตัวแปร จากข้อมูลที่ส่งกลับมา
โดยโค้ดเดิมก่อนปรับ ของเราจะเป็น ดังนี้
 
    this.provinceService.getProvince().subscribe((data) =>{
      this.results = data;
    });
 
แต่เนื่องจากรูปแบบข้อมูล json data จาก api มีการปรับเปลี่ยน เราจึงต้องกำหนดรูปแบบใหม่เล็กน้อย
ให้สอดคล้องสัมพันธ์กัน อย่าง result ในบรรทัดที่ 48 เรากำหนด type ให้มีรูปแบบตาม interface ที่เรากำหนด
เพื่อใช้สำหรับอ้างอิงและเรียกใช้ข้อมูล นอกจากนั้น เมื่อโหลดข้อมูลมาครั้งแรก เราก็นำค่าต่างๆ ที่จะใช้ไปกำหนด
ให้กับตัวแปรที่เราเตรียมไว้

    this.provinceService.getProvince().subscribe((result:resultData) =>{
      this.page = result.curPage;
      this.totalData = result.totalData;
      this.totalPage = result.totalPage;
      this.results = result.data;
    });     
 
เรากำหนดรูปแบบคล้ายกันนี้ในฟังก์ชั่น getItems() ซึ่งเป็นการค้นด้วยคำหรือข้อความ และในฟังก์ชั่น doRefresh()
เพื่อโหลดข้อมูลใหม่ แต่ในสองฟังก์ชั่นนี้ เรามีการรีเซ็ตค่า page ให้เป็น 1 คือให้เป็นการเริ่มต้นรายการใหม่จากหน้า
แรกของข้อมูล (ดังโค้ดในบรรทัดที่ 91 และ 105)
 
และโค้ดส่วนสุดท้าย บรรทัดที่ 125 - 142 จะเป็นส่วนของฟังก์ชั่น doInfinite() จะเห็นว่า การใช้งาน ก็จะยังคล้าย
กับสองฟังก์ชั่นด้านบน แต่ต่างตรงในส่วนของหน้า page เราจะทำการ เพิ่มค่าเข้าไป 1 ค่า เข้าใจง่ายๆ สมมติ
ตอนนี้เราอยู่หน้าแรก พอเราเลื่อนลงมาล่างสุด ก็จะเข้าฟังก์ชั่น doInfinite() เพื่อที่จะไปทำการดึงข้อมูลหน้าสอง
เราก็ต้องเพิ่มค่า page เพื่อให้มีค่าเท่ากับ ค่าในหน้าถัดไป ซึ่งก็คือ 2
    
  doInfinite(infiniteScroll) {
    this.page = this.page+1;
    let q = this.queryText;
    let sort = this.sorttype;
    let page = this.page;
    this.provinceService.getProvince(q,sort,page)
    .subscribe((result:resultData) =>{
      this.page = result.curPage;
      this.totalData = result.totalData;
      this.totalPage = result.totalPage;
      for(let i=0; i<this.totalData; i++) { 
        this.results.push(result.data[i]);
      }      
      infiniteScroll.complete();
      
    });    

  }
 
แต่ข้อแตกต่างอีกส่วนของฟังก์ชั่นนี้คือ เราไม่ได้นำค่า result.data มากำหนดค่าเพื่อไปใช้งานโดยตรงเหมือนหน้าแรก
เพราะถ้าเรากำหนดค่าแบบไปแทนค่าแบบหน้าแรก คือใช้ในรูปแบบ
 
this.results = result.data;
 
จะกลายเป็นเอาข้อมูลไปแทนที่ข้อมูลเก่า ซึ่งไม่ใช่วิธีที่ถูกต้อง สิ่งที่เราต้องการคือ เอาข้อมูลใหม่ ต่อเข้าไปจากข้อมูลเดิม
โดยการใช้ for() วนลูปตามจำนวนรายการที่ส่งกลับมา แล้วนำค่านั่น push เข้าไปใน array ของ results ทำแบบนี้ ก็จะได้
ข้อมูลของหน้าที่สองต่อเข้าไปจากข้อมูลหน้าแรก วนแบบนี้ไปเรื่อยๆ จนกว่าข้อมูลจะแสดงหมด 
    ในการดึงข้อมูลแต่ละหน้ามาแสดง เราจะจบด้วย infiniteScroll.complete() ซึ่งคล้ายกับ refresher หรือก็คือให้ปิดตัว
loading ของ infinite scroll หลังดึงข้อมูลเรียบร้อยแล้ว
 
เรามาดูตัวอย่างผลลัพธ์ ตามรูปภาพด้านล่างนี้
 
 
    

 
 
จะเห็นว่ารูปแรก มีรายการในหน้าแรกถูกนำมาแสดงแค่ 15 รายการ ตามที่เรากำหนดใน service api สังเกตส่วนของ scrollbar
ในภาพ พอเราเลื่อนลงมา ณ ตำแหน่งหนึ่งซึ่งห่างจากด้านล่างประมาณ 150px ก็จะแสดงการ loading ข้อมูลในหน้าต่อไปใน
ทันที และรูปที่สาม รายการข้อมูลก็จะถูกนำมาแสดงต่อจากข้อมูลเดิม 
 
 
ตอนนี้เราได้เรียนรู้ และเข้าใจลูกเล่นการใช้งานเพิ่มเติม สามารถนำไปประยุกต์ต่อยอดได้ รวมทั้งเราได้แทรกการใช้งาน component
ต่างๆ ประกอบเข้ามาในเนื้อหา เช่น การใช้งาน toast เพื่อขึ้นข้อความแจ้งสถานะการทำงาน เหล่านี้ล้วนเป็นแนวทางการประยุกต์
ที่มีประโยชน์ไม่มากก็น้อย 
   สำหรับเนื้อหาต่อไป จะเป็นเกี่ยวกับอะไร รอติดตาม
 


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



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









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









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





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

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


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


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







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