เนื้อหาต่อไปนี้ เราจะมาดูในส่วนของการส่งค่า parameter ไปพร้อมกับ path ของ route โดยจะยกเนื้อหา
ต่อจากตอนที่แล้ว เกี่ยวกับ product
การใช้งาน Multiple Routing Module ใน Angular ตอนที่ 3 http://niik.in/843
https://www.ninenik.com/content.php?arti_id=843 via @ninenik
โดยในส่วนนี้ เราจะสนใจเฉพาะส่วนของ product module โดยจะทำการจำลองข้อมูล product อย่างง่ายมา
ประกอบการอธิบาย
การสร้าง Class ไฟล์ใน Module
เริ่มต้นเราจะสร้าง product class เพื่อกำหนดรูปแบบของข้อมูล product ที่ต้องการ ด้วยคำสั่ง
C:\projects\simplerouter>ng g class product/product
เราจะได้ไฟล์ product.ts ในโฟลเดอร์ product module ให้แก้ไข ปรับการกำหนดรูปแบบ product class เป็นดังนี้
ไฟล์ product.ts
export class Product { constructor( public id:number, public name:string, public detail:string, public price:number, public quantity:number ){} }
คำแนะนำ: เราสามารถสร้างไฟล์ product.ts ที่จะใช้เป็นไฟล์ product class ผ่าน โปรแกรม VSCode แทนการ generate
ผ่าน angular cli ก็ได้
ต่อไปเราจะสร้างไฟล์ ข้อมูล (ซึ่งโดยปกติการใช้งานจริง ข้อมูลอาจจะมาจากฐานข้อมูลหรืออื่นๆ ) จำลองสำหรับทดสอบ
โดยให้เราสร้างไฟล์ mock-products.ts ไว้ใน product module คล้ายกับการสร้าง class
C:\projects\simplerouter>ng g class product/mock-products
ไฟล์ mock-products.ts
import { Product } from './product'; export var PRODUCTS:Product[] = [ { id:1, name:'โคมไฟตั้งโต๊ะ', detail:'รายละเอียด โคมไฟตั้งโต๊ะ', price:300, quantity:20 }, { id:2, name:'ไฟฉายพกพา', detail:'รายละเอียด ไฟฉายพกพา', price:200, quantity:50 }, { id:3, name:'แบตเตอรี่สำรอง', detail:'รายละเอียด แบตเตอรี่สำรอง', price:600, quantity:30 }, ]; /* public id:number, public name:string, public detail:string, public price:number, public quantity:number */
เนื้องจากเราต้องการตัวแปรข้อมูลไปใช้ในการทดสอบ ในส่วนนี้จึงเป็นการ export ตัวแปร ชื่อ PRODUCTS
ไม่ได้เป็นการ export class เหมือนไฟล์ product.ts
การสร้าง Service ใน Module
เมื่อเราได้ส่วนของข้อมูล และ รูปแบบข้อมูล แล้ว ต่อไปเราจะสร้าง ไฟล์ service สำหรับใช้ในการจัดการกับข้อมูล
เช่น การดึงรายการข้อมูลทั้งหมด หรือดึงข้อมูลบางส่วน
ให้เราสร้างไฟล์ product.service.ts ด้วยคำสั่ง
C:\projects\simplerouter>ng g service product/product
ไฟล์ product.service.ts
import { Injectable } from '@angular/core'; import { PRODUCTS } from './mock-products'; @Injectable() export class ProductService { constructor() { } getProducts(){ return PRODUCTS; } getProduct(id:number|string){ return PRODUCTS.filter( function(product,i){ return product.id == id ? product : null; } ); } }
ในไฟล์ product service เรา import ตัวแปร PRODUCTS ข้อมูลตัวอย่าง เข้ามาใช้งาน
และกำหนดฟังก์ชั่นคำสั่ง 2 คำสั่ง โดยคำสั่งแรกเป็นการ ส่งค่าข้อมูลทั้งหมด ไปใช้ ด้วยคำสั่ง
getProducts() และคำสั่งที่สอง เป็นการส่งข้อมูล แบบมีเงื่อนไขการรับค่า parameter ที่ส่งเข้ามา เป็น
ตัวแปร id โดยให้ชนิดของต้วแปร id นั้นเป็นได้ทั้ง number หรือ string ก็ได้ จากนั้นใช้ฟังก์ชั่น filter()
กรองและส่งกลับเฉพาะข้อมูลที่ id ของข้อมูลตรงกับ id ของ parameter ที่ส่งเข้ามา
การใช้งาน Service ใน Module
หลังจากที่เราได้ส่วนของข้อมูล และการจัดการข้อมูลแล้ว ต่อไปเราจะนำ product service ไปใช้งาน
ใน product module โดย import และเพิ่มเข้าไปในส่วนการกำหนด providers
ไฟล์ product.module.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ProductRoutingModule } from './product-routing.module'; import { ProductListComponent } from './product-list.component'; import { ProductDetailComponent } from './product-detail.component'; import { ProductService } from './product.service'; @NgModule({ imports: [ CommonModule, ProductRoutingModule ], providers: [ ProductService ], declarations: [ProductListComponent, ProductDetailComponent] }) export class ProductModule { }
หลังจากที่เรา import ProductService เข้ามาใช้งานใน product module แล้ว เราก็จะสามารถใช้งาน
คำสั่งต่างๆ ใน ProductService ที่เราสร้างไว้ มาใช้งานใน Component ได้
การใช้งาน ActivateRoute, Router, และ ParamMap ใน Component
เริ่มต้นที่ component แรก ไฟล์ product-list.component.ts ดูโค้ดไฟล์ทั้งหมด และรายละเอียดคำอธิบาย
เพิ่มเติมด้านล่าง
ไฟล์ product-list.component.ts
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, ParamMap } from '@angular/router'; import { Product } from './product'; import { ProductService } from './product.service'; @Component({ selector: 'app-product-list', templateUrl: './product-list.component.html', styleUrls: ['./product-list.component.css'] }) export class ProductListComponent implements OnInit { public products:Product[]; private highlightId:number|string; constructor( private productService: ProductService, private route: ActivatedRoute, private router: Router ) { } ngOnInit() { this.products = this.productService.getProducts(); let params = this.route.snapshot.paramMap; if(params.has('id')){ this.highlightId = params.get('id'); } //console.log(this.products); //console.log(this.route); } }
บรรทัดที่ 2 เราทำการ import Router, ActivatedRoute, และ ParamMap ที่อยู่ใน router package
เข้ามาใช้งาน
โดยส่วนของ Router จะใช้สำหรับทำคำสั่งเปลี่ยน path หริอลิ้งค์ไปหน้าที่ต้องการ ผ่านคำสั่ง
navigate() ตัวอย่างการใช้งาน เช่น
this.router.navigate(['/product/1']); // กรณีมี parameter ที่จำเป็นต้องมี // หรือ this.router.navigate(['/product',id]); // กรณีมี parameter ที่จำเป็นต้องมี // หรือ this.router.navigate(['/products']); // กรณีไม่มี parameter ใดๆ // หรือ this.router.navigate(['/products',{id:id,more:'test'}]); // กรณีมี parameter option มีหรือไม่ก็ได้
ตัวอย่างผลลัพธ์ของ url กรณีมี และ ไม่มีการส่ง parameter
http://localhost:4200/product/1 http://localhost:4200/product/1 http://localhost:4200/products http://localhost:4200/products;id=3;more=test
โดยในส่วนของ ActivatedRoute service จะเป็นส่วนที่ให้ข้อมูลต่างๆ เกี่ยวกับ route ไม่วาจะเป็น route path
หรือ parameter ที่ส่งมาพร้อมกับ route และข้อมูลอื่นๆ
ตัวอย่างโค้ดอ้างอิงข้อมูล paramMap ของ ActivatedRoute
let params = this.route.snapshot.paramMap;
และสุดท้ายในส่วนของ ParamMap จะมีคำสั่งในการจัดการค่าของ parameter เช่น has() ,get() หรือ getAll()
เป็นต้น ดูตัวอย่างการใช้ฟังก์ชั่นของ ParamMap
let params = this.route.snapshot.paramMap; if(params.has('id')){ this.highlightId = params.get('id'); }
ต่อมาบรรทัดที่ 4 และ 6 เป็นการ import product class และ product service มาใช้งาน ตามลำดับ
สำหรับในส่วนของ ProductListComponent class เราสร้างตัวแปร products และ highlightId
โดย
products สำหรับเก็บข้อมูลรายการ product
highlightId สำหรับเก็บ id ที่ถูกส่งมาจากหน้า รายละเอียด ว่าเราเพิ่งเปิด product id ไหน
public products:Product[]; private highlightId:number|string;
ส่วนต่อมาเป็นส่วนของ constructor ฟังก์ชั่น เราจะ inject service ที่จะใช้งาน
constructor( private productService: ProductService, private route: ActivatedRoute, private router: Router ) { }
ตัวแปร productService , route และ router คือชื่อที่เรากำหนดใช้แทน service ต่างๆ ที่ inject เข้ามา
และส่วนสุดท้ายในไฟล์ product-list.component.ts จะเป็นส่วนการทำงานเมื่อเริ่มต้นเรียกใช้งาน
ProductListComponent
ngOnInit() { this.products = this.productService.getProducts(); let params = this.route.snapshot.paramMap; if(params.has('id')){ this.highlightId = params.get('id'); } //console.log(this.products); //console.log(this.route); }
โดยเราทำการดึงรายการ product ทั้งหมดผ่าน productService แล้วมาเก็บไว้ในตัวแปร products
this.products = this.productService.getProducts();
ต่อด้วยส่วนของการใช้งาน ActivatedRoute ผ่านตัวแปร route โดยดึงค่าข้อมูล paramMap มาไว้ใน
ตัวแปร params แล้วทำการตรวจสอบว่า ตัวแปร params นั้นมี property ที่ชื่อ id หรือไม่ ถ้ามี
ก็ให้เก็บค่า id ไว้ในตัวแปร highlightId เป็นค่าที่ใช้สำหรับทำ highlight แถวของข้อมูลรายการ
product ที่เราเพิ่มเข้าชม
let params = this.route.snapshot.paramMap; if(params.has('id')){ this.highlightId = params.get('id'); }
หลังจากเราจัดการในส่วนการทำงานของไฟล์ product list เรียบร้อยแล้ว ต่อไป ก็เป็นส่วนของการ
แสดงผลของ product list ให้เราเปิดไฟล์ product-list.component.html มาแก้ไขเป็นดังนี้
ไฟล์ product-list.component.html
<p> product-list works! </p> <table class="table"> <thead> <tr class="active"> <th>#</th> <th>Name</th> <th>Price</th> <th>View</th> </tr> </thead> <tbody> <tr *ngFor="let product of products; let i=index;" [class.warning]="highlightId == product.id" > <td>{{ i+1 }}</td> <td>{{ product.name }}</td> <td>{{ product.price }}</td> <td> <button type="button" routerLink="/product/{{ product.id }}" class="btn btn-sm btn-success"> View </button> </td> </tr> </tbody> </table>
รายละเอียดการใช้งานหน้านี้ จะไม่ขออธิบายอะไรมาก เพราะเป็นข้อมูล และวิธีการใช้งานที่เคยผ่านตามาแล้ว
ดูในส่วนของการ กำหนด class ให้กับ แถวที่เป็นรายการที่เพิ่งเปิดเข้ามา
[class.warning]="highlightId == product.id"
เป็นรุปแบบการใช้งาน Data Binding อ่านเพิ่มเติมได้ที่
การเชื่อมโยงข้อมูล Data Binding รูปแบบต่างๆ ใน Angular http://niik.in/787
https://www.ninenik.com/content.php?arti_id=787 via @ninenik
โดยถ้าค่า highlightId ที่ได้จาก paramter ถ้ามีการส่งกลับมา ตรงกับค่า product.id ของแถวข้อมูลไหน
ให้แถวนั้น ใช้งาน css class ที่ชื่อ warrning เพื่อทำการ highlight รายการนั้น
ในหน้ารายการข้อมูลหน้านี้ เราจะมีการลิ้งค์ไปหน้ารายละเอียดข้อมูลผ่าน การกำหนด ลิ้งค์ในปุ่ม button
<button type="button" routerLink="/product/{{ product.id }}" class="btn btn-sm btn-success"> View </button>
โดยมีการส่งไปที่ path /product:id มีค่า id เป็น parameter ส่งไปด้วย เพื่อใช้ในการดึงข้อมูลเฉพาะรายการ
product id ที่เราคลิกเลือก
มาต่อที่ส่วนของการแสดงรายละเอียดรายการ product หรือ หน้า product detail ให้เราปรับโค้ดไฟล์
product-detail.component.ts เป็นดังนี้
ไฟล์ product-detail.component.ts
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute, ParamMap } from '@angular/router'; import { Product } from './product'; import { ProductService } from './product.service'; @Component({ selector: 'app-product-detail', templateUrl: './product-detail.component.html', styleUrls: ['./product-detail.component.css'] }) export class ProductDetailComponent implements OnInit { public products:Product[]; private id:number|string; constructor( private productService:ProductService, private route: ActivatedRoute, private router: Router ) { } ngOnInit() { let params = this.route.snapshot.paramMap; if(params.has('id')){ this.id = params.get('id'); } this.products = this.productService.getProduct(this.id); // console.log(this.id); // console.log(this.products); // console.log(this.route); } gotoProducts() { this.router.navigate(['/products']); } gotoProducts2(){ this.router.navigate(['/products',{id:this.id,more:'test'}]); } }
รูปแบบการใช้งานจะคล้ายๆ กับในส่วนของไฟล์ product-list.component.ts แต่ในไฟล์นี้ เราจะใช้คำสั่ง
getProduct() ที่มีการระบุ id ของ product เพื่อดึงข้อมูล เฉพาะรายการที่ต้องการมาแสดง โดยตัวแปร id
ได้จาก parameter ที่ส่งเข้ามาพร้อมกับ routing path
พอได้ข้อมูลของ product เราจะเก็บไว้ในตัวแปร products ซึ่งเป็น array ที่มีรายการ prodcut แค่รายการ
เดียว ค่าตัวแปร products นี้เราจะนำไปแสดงผลต่อไป
นอกจากนั้นเรายังมีการสร้างฟังก์ชั่น เพิ่มเข้ามาอีก 2 อัน เพื่อทดสอบการใช้งาน การใช้คำสั่ง navigate() ของ
Router service โดยฟังก์ชั่นแรก gotoProducts() ไม่มีการส่งค่าใดๆ กลับไปยัง หน้า /products หรือก็คือหน้า
product list ส่วนฟังก์ชั่นที่สอง gotoProducts2() เราทดสอบแบบมีการส่งค่า parameter กลับไป เพื่อใช้สำหรับ
ทำการ highlight รายการที่เพิ่งเปิดดู โดยในตัวอย่างจะส่ง 2 ค่า คือ id และ more
ตัวอย่างเช่น หลังจากเรากดเข้าไปดูรายละเอียด ของ product id เท่ากับ 2 แล้วเรากดปุ่ม ที่เรียกใช้ฟังก์ชั่น
getProduct2() จะได้ url ที่มีการส่งค่ากลับมาเป็นดังนี้
http://localhost:4200/products;id=2;more=test
ต่อไปเป็นส่วนสุดท้าย คือไฟล์แสดงรายละเอียดของรายการ product
ไฟล์ product-detail.component.html
<p> product-detail works! </p> <table class="table" *ngFor="let product of products"> <tr> <th class="active text-right" width="120">ID</th> <td>{{ product.id }}</td> </tr> <tr> <th class="active text-right" width="120">Name</th> <td>{{ product.name }}</td> </tr> <tr> <th class="active text-right" width="120">Detail</th> <td>{{ product.detail }}</td> </tr> <tr> <th class="active text-right" width="120">Price</th> <td>{{ product.price }}</td> </tr> <tr> <th class="active text-right" width="120">Quantity</th> <td>{{ product.quantity }}</td> </tr> </table> <button class="btn" type="button" routerLink="/products">Back 1</button> <button class="btn btn-info" type="button" (click)="gotoProducts()">Back 2</button> <button class="btn btn-warning" type="button" (click)="gotoProducts2()">Back 3</button>
จากโค้ด้านบน จะเห็นว่าปุ่ม Back ที่ 1 และ 2 ทำงานเหมือนกัน คือลิ้งค์กลับมาหน้า product list โดยปุ่ม
แรกใช้วิธีการกำหนดผ่าน routerLink directive ระบุ path ที่ต้องการกลับไปโดยตรง ส่วนปุ่มที่ 2 จะเป็น
ใช้งานฟังก์ชั่น gotoProducts() ที่ใช้คำสั่ง navigate() ของ router เปลี่ยนหน้าแสดงข้อมูล
ส่วนปุ่มที่ 3 เป็น ปุ่มที่เราทดสอบส่งค่า parameter เพิ่มเติม กลับมายังหน้า product list เรียกใช้ผ่าน
event click เรียกฟังก์ชั่น gotoProducts2()
ตอนนี้ App ของเราก็พร้อมทำงานแล้ว ทดสอบการทำงานได้ที่ demo ด้านล่าง คลิกเลือกไปที่เมนู product
ดูผลลัพธ์การทำงาน
เนื้อหาบทความในตอนนี้ที่ได้ศึกษาข้างต้น จะไม่ได้ใช้ RxJs มาช่วย ทั้งนี้ก็เพระาไม่ให้ต้องจำในส่วนของ
คำสั่งต่างๆ มากเกินไป จึงใช้วิธีอย่างง่ายมาประกอบคำอธิบาย แต่อย่างไรก็ตาม การใช้งานฟังก์ชั่น และการจัดการ
ข้อมูลผ่าน RxJS ก็เป็นวิธีที่จะช่วยให้เราทำงานได้เร็วและสะดวกขึ้น มีโอกาสจะนำเสนอในลำดับต่อๆ ไป
สำหรับเนื้อหาในการใช้งาน routing น่าจะยังไม่จบแค่นี้ ยังไงรอติดตามตอนต่อไป