เนื้อหาในตอนต่อไปนี้ เราจะพูดถึงกรณีที่เราจะต้องทำการนำเข้า service ที่ไม่ได้
เป็น class มากำหนดใน provider เช่น เป็น string function หรือ object
บทความต่อเนื่องจากตอนที่แล้ว
(ทบทวนบทความที่)
การเรียกใช้งาน service ในอีก service และรู้จักกับ provider เพิ่มเติม
https://www.ninenik.com/content.php?arti_id=784 via @ninenik
การกำหนด และใช้งาน Value provider
สมมติเรา export ค่าเป็น object ชื่อ objLogger เพิ่มลงไปในไฟล์ logger.service.ts
ด้วยโค้ดตัวอย่างดังนี้
export let objLogger = { logs: ["ดึงข้อมูลโดยใช้ผ่าน 'useValue' "], log: () => { console.log("use method in objLogger"); } };
ใน objLogger ก็จะมี property 2 ตัวคือ logs มีค่าเป็น ชุดข้อมูล Array
และอีกอันคือ log มีค่าเป็นฟังก์ชั่น รูปแบบ () => {} เป็นรูปแบบการเขียนฟังก์ชั่น
ใหม่ใน ES6 หรือเรียกรูปแบบฟังก์ชั่นนี้ว่า arrow function
รูปแบบ arrow function ใน ES6 หรือ JavaScript เวอร์ชั่นใหม่
() => { ... } // ไม่มี parameter x => { ... } // มี 1 parameter (x, y) => { ... } // มี parameter มากกว่า 1
ตัวอย่างการกำหนดค่าในวงเล็บปีกกา:
x => { return x * x } // กำหนดแบบ block กรณีรูปแบบนี้ x => x * x // สามารถแทนด้วยรูปแบบนี้แทน
รูปแบบเต็มที่ใช้ใน ES5
function(x){ return x * x; }
เรากลับมาที่โค้ดเราต่อ
log ฟังก์ชั่น จะทำงานแสดงข้อความทาง console
ไฟล์ logger.service.ts ปรับใหม่ ตัด NewLogger และ EvenBetterLogger ออก
ไฟล์ logger.service.ts
import { Injectable } from '@angular/core'; import { Optional } from '@angular/core'; import { BestStaffService } from './components/beststaff.service'; @Injectable() export class Logger { logs: string[] = []; // กำหนดตัวแปร array แบบ string log(message: string) { // ฟังก์ชั่น log() this.logs.push(message); // รับค่าข้อความและเพิ่มไปใน array console.log(message); // แสดงข้อความใน console } } export let objLogger = { logs: ["ดึงข้อมูลโดยใช้ผ่าน 'useValue' "], log: () => { console.log("use method in objLogger"); } };
จากนั้นให้เราทำการกำหนดค่าใน provider ในไฟล์ app.module.ts ดังนี้
ไฟล์ app.module.ts ตัดโค้ดตัวอย่างมาเฉพาะในส่วนของ providers
ไฟล์ app.module.ts
providers:[ BestStaffService, { provide: Logger, useValue: objLogger } ],
จากรูปโค้ดตัวอย่างเราตัด NewLogger และ EvenBetterLogger ออก แล้ว import objLogger
จากไฟล์ logger.service.ts มาใช้ จากนั้นส่วนของโค้ดการกำหนด providers เรามีการใช้
useValue property เพื่อกำหนดค่า objLogger ถูกอ้างอิงด้วยตัวแปร provider ที่ชื่อ Logger
เราเรียก Logger ที่ใช้ค่าจาก Object หรือจากตัวแปร ว่า Value provider
ทดสอบรันดูผลลัพธ์ (อย่าลืมว่าไฟล์ staff.service.ts เรายังมีการเรียกใช้งานคำสั่ง log() )
จะเห็นว่าตัวแปรอ้างอิง Logger เรียกใช้คำสั่ง log() ในไฟล์ staff.service.ts ตามที่เราเคยบอกไว้แล้ว
และเมื่อตัวแปร Logger อ้างอิงค่าจาก objLogger Object จะทำให้ Logger ไปทำงานคำสั่ง log()
ใน objLogger แทน เพราะมีการกำหนดให้ Logger ใช้ค่าจาก objLogger Object ตามที่เราได้
กำหนดไปแล้วในค่า useValue
การกำหนด และใช้งาน Factory provider
ในบางครั้งเราต้องการค่าที่มีการเปลี่ยนแปลงตามข้อมูลที่มี สมมติว่า service ที่นำเข้า ไม่สามารถเข้า
ถึงข้อมูลต้นทางอย่างอิสระได้โดยตรง รูปแบบสถานการณ์ในลักษณะนี้ เราจะใช้ factory provider เข้ามาจัดการ
ลองมาดูข้อมูลตัวอย่าง
สมมติ เราต้องการจำกัดการแสดงข้อมูลของ staff ตามเงื่อนของ user หรือผู้ใช้งาน เช่น
ถ้า user คนนี้เข้ามาใช้งาน ให้แสดงเฉพาะ staff ที่มีอายุมากกว่าหรือเท่ากับ 30 ปี เป็นต้น
ให้เราสร้างไฟล์ user.service.ts ไว้ในโฟลเดอร์ components ดังนี้
ไฟล์ user.service.ts
import { Injectable } from '@angular/core'; // ไม่กำหนด @Injectable() ตรงนี้เพราะเราไม่ได้จะใช้ User class เป็น service export class User { constructor( public name: string, public isAuthorized = false) { } } // สร้าง user สมมติขึ้นมาสองคน let Manop = new User('Manop', true); let Jubjang = new User('Jubjang', false); @Injectable() export class UserService { user = Jubjang; // กำหนด user เริ่มต้นเป็น Jubjang // คำสั่งสลับ user อย่างง่าย getNewUser() { // ทุกครั้งที่มีการเรียกคำสั่งนี้ทำงาน ค่า user จะสลับไปมา return this.user = this.user === Jubjang ? Manop : Jubjang; } }
จากไฟล์ user.service.ts เราได้สร้าง user class โดยไม่ได้กำหนด @Injectable() เข้าไป
ทั้งนี้เพราะไม่ได้จะใช้งาน user class เป็น service แต่จะใช้เป็น Object class กำหนดคุณลักษณะ
ของ user ว่ามีอะไรบ้าง ในที่นี้ก็จะมี name กับ isAuthorized คือชื่อกับสิทธิการเข้าใช้งาน
หรือสิทธิการได้รับอนุญาติ
จากนั้นเราก็สร้าง user จำลองขึ้นมาสองค้น โดยกำหนดค่า parameter ตามโครงสร้างของ
Object user จากตัวอย่าง เราให้ Manop มีสิทธิการเข้าถึงข้อมูล เป็น true ส่วน Jubjang เป็น false
และใน UserService class เรากำหนดตัวแปร user เริ่มต้นเป็น Jubjang และมีคำสั่ง getNewUser()
ทำหน้าทีในการสลับ user เพื่อใช้ทดสอบค่าที่จะถูกส่งเข้าไปใช้งานใน factory provider
เป้าหมายของเราคือ เราต้องการให้ StaffService ซ่อนหรือแสดงรายชื่อ staff ตามเงื่อนไขของ user
เช่น user ที่มี isAuthorized เป็น true จะไม่ถูกจำกัดสิทธิสามารถดูรายชื่อ staff ได้ทั้งหมด
ส่วน user ที่มี isAuthorized เป็น false จะถูกจำกัดสิทธิ ในที่นี้เราจะกำหนดให้สามารถดูเฉพาะรายชื่อ
staff ที่มีอายุไม่ถึง 30 ปีเท่านั้น
สถานการณ์คือ StaffService จำเป็นต้องทราบข้อเท็จจริงของ user ว่า user นั้นๆ ถูกจำกัดสิทธิหรือไม่
โดยค่าสิทธิของ user นั้นสามารถเปลี่ยนแปลงเช่น มีการล็อกอิน user ใหม่เข้ามา
แต่ปัญหาคือ เราไม่สามารถนำเข้า UserService เข้าไปใน StaffService ได้ ทำไห้ StaffService
เข้าไม่ถึงข้อมูลของ user เพื่อดูว่า user ไหนถูกจำกัดสิทธิหรือไม่ถูกจำกัดสิทธิ
ดังนั้น StaffService จะใช้ตัวแปรค่าที่เป็น boolean ที่เป็น true หรือ false แทน ในการใช้เป็นเงื่อนไข
การจำกัดสิทธิของ user ที่จะถูกส่งค่าเข้ามา ปรับไฟล์ staff.service.ts ดังนี้
ไฟล์ staff.service.ts
import { Injectable } from '@angular/core'; import { STAFFS } from './mock-staffs'; import { Logger } from '../logger.service' @Injectable() export class StaffService { constructor( private logger: Logger, private isAuthorized:boolean) { } getStaffs() { let auth = this.isAuthorized ? 'ไม่ถูกจำกัดสิทธิ ' : 'ถูกจำกัดสิทธิ'; this.logger.log(`เรียกดูข้อมูลจากผู้ใช้ที่ ${auth} `); return STAFFS.filter(staff => this.isAuthorized || !(staff.age>=30)); } }
สังเกตว่า StaffService มีการกำหนดตัวแปร 2 ตัวไว้ใน constructor()
ตัวแปร loggger อ้างอิง Logger class นำเข้ามาเพื่อใช้งานคำสั่ง log() แสดงข้อมูลทาง console
และตัวแปร isAuthorized เป็นตัวแปร boolean เป็นค่าของสิทธิของ user เป็น true หรือ false
ต่อมาในคำสั่ง getStaffs() มีการกำหนดตัวแปร auth (การกำหนดตัวแปรโดยใช้ let หมายถึง
ตัวแปรนั้นๆ จะใช้งานได้เฉพาะใน block นั้น ๆ ในตัวอย่างคือใช้ได้เฉพาะใน คำสั่ง getStaffs() )
ตัวแปร auth กำหนดค่ามาเก็บข้อความ string ที่มีเงื่อนไขว่าถ้า ตัวแปร isAuthorized ที่ส่งเข้ามา
เป็น true ให้มีค่าเป็น 'ไม่ถูกจำกัดสิทธิ ' และถ้ามีค่าเป็น false ให้เป็น 'ถูกจำกัดสิทธิ'
บรรทัดต่อมาให้ทำการแสดงข้อความออกทาง console โดยเรียกใช้คำสั่งจาก Logger class ผ่าน
ตัวแปร logger ให้ทำงานตามคำสั่ง
this.logger.log(`เรียกดูข้อมูลจากผู้ใช้ที่ ${auth} `);
ตัวแปรค่า auth กรณีส่งเข้าไป string ให้ใช้รูปแบบ ${xxxxx}
บรรทัดสุดท้ายในคำสั่ง getStaffs() เป็นการกำหนดให้ แสดงรายชื่อ staff ตามเงื่อนไข ที่เราต้องการ
ซึ่งในที่นี้กำหนดว่าถ้า isAuthorized เป็น true ก็แสดงรายชื่อทั้งหมด หรือ ถ้า isAuthorized เป็น false
ให้แสดงราชื่อ เฉพาะ staff ที่มีอายุน้อยกว่า 30 ปี
!(staff.age>=30) // หมายถึง ไม่ใช่ staff ที่มีอายุมากกว่า 30 ปี // มีค่านเช่นเดียวกับ staff.age<30 // หมายถึง staff ที่อายุน้อยกว่า 30 ปี
เรามาทำความคุ้นเคยกับ JavaScrpt เพิ่มเติม
STAFFS.filter(staff => this.isAuthorized || !(staff.age>=30));
ตัวแปร STAFFS เป้นข้อมูล Array ของ Object ใช้ฟังก์ชั่นที่ชื่อ filter() ในการกรองรายการ Array ที่ต้องการ
ยกตัวอย่างเช่น
// ฟังก์ชั่นคืนอค่ารายการที่มากกว่าหรือเท่ากับ 10 function isBigEnough(value) { return value >= 10; } // ตัวแปร Array ที่ชื่อ filtered ใช้ฟังก์ชั่น filter() วนลูปส่งค่าเข้าไปในฟังก์ชั่น isBigEnough // แล้วคืนค่ากลับมาเฉพาะกรณีค่าเป็นจริงตามเงื่อนไขฟังก์ชั่น isBigEnough คือต้องมีค่า >=10 var filtered = [12, 5, 8, 130, 44].filter(isBigEnough); // ผลลัพธ์ค่าของตัวแปร filtered ก็จะได้เป็นดังนี้ // filtered มีค่าเท่ากับ [12, 130, 44]
และจากที่เราพอรู้ไปบ้างแล้วเกี่ยวกับฟังก์ชั่น ใน ES6 รูปแบบใหม่
staff => this.isAuthorized || !(staff.age>=30)
จึงหมายถึง
function(staff){ return this.isAuthorized || !(staff.age>=30); }
คำสั่ง
STAFFS.filter( function(staff){ return this.isAuthorized || !(staff.age>=30); } )
จึงหมายถึง ตัวแปร STAFFS ส่งค่า Object ของแต่ละ Array เข้าไปในฟังก์ชั่น ในชื่อ staff
วนลูปทำคำสั่ง ส่งค่าคืนกลับมา เมื่อ ค่า this.isAuthorized || !(staff.age>=30) มีค่าเป็น true
โดยเงื่อนไข || จะเป็น true เมื่อค่าใด ค่าหนึ่งเป็น true
ดังนั้นถ้าเป็น user ที่มี isAuthorized เป็น true ค่าก็จะเป็น true ทั้งหมด
โดยไม่ต้องสนใจอายุของ staff
this.isAuthorized || !(staff.age>=30) // true || !(staff.age>=30)
ค่าเป็น true ทั้งหมดก็หมายถึง ส่งรายชื่อ staff ทั้งหมดมาแสดง
แต่ถ้าสมมติว่า user มี isAuthorized เป็น false ค่าจะเป็น true ก็ต่อเมือ !(staff.age>=30) เป็น true
this.isAuthorized || !(staff.age>=30) // false || !(staff.age>=30) // !(staff.age>=30) ต้องเป็น true จริงจะคืนค่ากลับมา ดังนั้น // staff ที่ไม่ได้มีอายุมากกว่าหรือเท่ากับ 30 จะเป็นค่า true นั่นคือ แสดงเฉพาะ staff ที่อายุน้อยกว่า 30
ถือเป็นการทบทวนเกี่ยวกับเงื่อนไข "หรือ"( || ) เป็น true มื่อมีค่าใดค่าหนึ่งเป็น true
กลับมาต่อที่บทความ จากโค้ดไฟล์ staff.service.ts เราสามารถนำเข้า Loggger class ได้ แต่เราไม่สามารถนำเข้า
ค่าของตัวแปร isAuthorized ซึ่งเป็น boolean ได้
ดังนั้นเราต้องทำการสร้างตัวแปรอ้างอิงของ StaffService ขึ้นมาใหม่ ทีมีฟังก์ชั่นในการจัดการกับค่า
isAuthorized ซึ่งวิธีการนี้เราเรียกว่าการใช้งาน factory provider หรือการสร้าง provider ที่เปลี่ยนแปลง
ค่าไปตามเงื่อนไขหรือข้อมูลที่กำหนด
ให้เราสร้างไฟล์ชื่อ staff.service.provider.ts ไว้ในโฟลเดอร์ components ดังนี้
ไฟล์ staff.service.provider.ts
import { StaffService } from './staff.service'; import { Logger } from '../logger.service'; import { UserService } from './user.service'; let staffServiceFactory = (logger: Logger, userService: UserService) => { return new StaffService(logger, userService.user.isAuthorized); }; export let staffServiceProvider = { provide: StaffService, useFactory: staffServiceFactory, deps: [Logger, UserService] };
ไฟล์ staff.service.provider.ts ไม่ใช่ไฟล์ class เป้นไฟล์ที่สร้างตัวแปร Object ที่ชื่อ staffServiceProvider
เพื่อไปใช้งานเป็น provider object สังเกตว่ารูปแบบจะเหมือนกับการกำหนด provider แบบ Object
{ provide: StaffService,useFactory: staffServiceFactory,deps: [Logger, UserService] }
แต่รูปแบบที่แตกต่างคือ มีการใช้ useFactory และ deps โดย
useFactory เป็น factory fucntion ที่ทำการสร้างตัวแปรอ้างอิง StaffService ใหม่ตามเงื่อนไขค่า
isAuthorized
deps เป็น Array ของ class ที่จำเป็นต้องใช้งานร่วม ในตัวอย่างใช้ Logger และ UserService
เรามาดูว่า ทำไมถึงใช้ชื่อไฟล์ว่า staff.service.provider.ts ซึ่งคล้ายๆ กับ staff.service.ts ทั้งนี้ก็เพราะ
เราจะสร้างตัวแปรอ้างอิงของ StaffService ทำให้เหมือนได้ StaffService class ใหม่ที่มีค่าเปลี่ยนแปร
ตามเงื่อนไขค่า isAuthorized
โดยไฟล์ staff.service.provider.ts มีการ import class ต่างๆ ที่จำเป็นเข้ามาใช้งาน
จากนั้นทำการสร้างตัวแปรชื่อ staffServiceFactory เป็นฟังก์ชั่น รูปแบบ ES6
let staffServiceFactory = (logger: Logger, userService: UserService) => { return new StaffService(logger, userService.user.isAuthorized); };
มาจาก
let staffServiceFactory = function(logger: Logger, userService: UserService){ return new StaffService(logger, userService.user.isAuthorized); };
ฟังก์ชั่นนี้เรียกว่า factory function รองรับฃ parameter 2 ตัว ที่เป็น Logger และ UserService
โดยเมื่อรับค่าเข้าไปในฟังก์ชั่นแล้ว เราก็สามารถที่จะนำค่า userService.user.isAuthorized
ซึ่งเป็นค่าที่ต้องการ ในการใช้กำหนดสิทธิผู้ใช้ เข้าไปใน StaffService parameter ใน constructor()
ฟังก์ชั่นที่เราได้กำหนดไปแล้วในไฟล์ staff.service.ts จากนั้นก็ return ค่าออกมา
นั่นคือ factory function เสมือนสร้าง class StaffService ขึ้นมาใหม่ตามเงื่อนไขที่เรากำหนด
ซึ่งพอเราได้ class มาแล้ว เราก็สามารถนำไปใช้ในการขึ้นทะเบียน provider ได้
export let staffServiceProvider = { provide: StaffService, useFactory: staffServiceFactory, deps: [Logger, UserService] };
ดังนั้นส่วนนี้ก็คือได้ Provider Object ที่มีชื่อตัวแปรอ้างอิงสำหรับเรียกใช้ชื่อว่า StaffService
ใช้ class ที่ได้จากฟังก์ชั่น staffServiceFactory ที่กำหนดด้วย useFactory
และนำเข้า class Logger , UserService มาใช้งานร่วม
ต่อไปเราจะมาทำการขึ้นทะเบียน Provider ซึ่งในที่นี้เราจะไม่ได้กำหนดในไฟล์ app.module.ts
แต่เราจะใช้เฉพาะใน @Component แทน แต่ก่อนอื่นให้เราแก้ไขไฟล์ app.component.ts
ใหม่เป็นดังนี้
ไฟล์ app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<staff></staff>` }) export class AppComponent { }
โดยเปลี่ยนส่วนของ template จากเดิม
template: `<staff-list></staff-list>`
ซึ่งเป็น
template: `<staff></staff>`
ทั้งนี้เพื่อเราจะได้สร้างส่วนจำลองการทำงานให้เห็นภาพ เพิ่มขึ้น เช่น มีปุ่ม
สมมติการล็อกอินของ user มีส่วนของการแสดง รายชื่อ staff ตามเงื่อนไข เป็นต้น
จากนั้นให้สร้างไฟล์ staff.component.ts ไว้ในโฟลเดอร์ components ดังนี้
ไฟล์ staff.component.ts
import { Component } from '@angular/core'; import { UserService } from './user.service'; @Component({ selector: 'staff', template: ` <br> <div class="container"> <button (click)="nextUser()" class="btn btn-sm btn-success"> Next User</button> </div> <staff-list id="authorized" *ngIf="isAuthorized"></staff-list> <staff-list id="unauthorized" *ngIf="!isAuthorized"></staff-list> `, }) export class StaffComponent { constructor(private userService:UserService){} get isAuthorized() { return this.user.isAuthorized; } nextUser() { this.userService.getNewUser(); } get user() { return this.userService.user; } }
ไฟล์ staff.component.ts มีการ import UserService มาใช้งาน กำหนด selector เป็น staff
คือให้ไปแสดงใน tag <staff></staff> ที่เรากำหนดในไฟล์ app.component.ts
ในส่วนของ StaffComponent class มีการนำเข้า UserService เข้ามาผ่าน constructor() ฟังก์ชั่น
มีการคืนค่า isAuthorized ของ user ปัจจุบันด้วยคำสั่ง
get isAuthorized() { return this.user.isAuthorized; }
สังเกตคำสั่ง get เป็นคำสั่งใน TypeScript ในการคืนค่าของฟังก์ชั่นออกมาทันทีเมื่อ
StaffComponent class มีการเรียกใช้งาน โดยไม่ต้องเรียกใช้ฟังก์ชั่น กล่าวคือ
ถ้าเราอ้างอิงตัวแปร isAuthorized ก็จะหมายถึงค่าจากฟังก์ขั่น โดยที่เราไม่ต้องใช้คำสั่งว่า isAuthorized()
เช่นเดียวกับคำสั่ง
get user() { return this.userService.user; }
การอ้างอิงค่าตัวแปร user จะหมายถึงค่าของฟังก์ชั่น user() ซึ่งจากโค้ดก็คือ คืนค่าเป็น User Object ณ
ขณะนั้น
ส่วนคำสั่ง
nextUser() { this.userService.getNewUser(); }
เป็นคำสั่งสำหรับสลับ user ที่เราได้กล่าวไปแล้วในตอนต้น โดยให้ UserService ทำคำสั่ง getNewUser()
อีกที คำสั่ง nextUser() นี้เราไม่ได้กำหนด get นำหน้า นั้นหมายถึงคำสั่งนี้ จะไม่ได้ทำงานทันที
แต่จะทำงานเมื่อเรามีการเรียกใช้ ซึ่งจะเห็นว่าใน template เราได้สร้างปุ่มขึ้นมา
และกำหนดให้เมื่อคลิกที่ปุ่ม ให้ทำการการทำคำสั่งนี้ เพื่อสลับ user
นอกจากนั้นเราได้กำหนดการแสดง tag <staff-list> ตามเงื่อนไข ค่าตัวแปร isAuthorized ด้วย
*ngIf directive
<staff-list id="authorized" *ngIf="isAuthorized"></staff-list> <staff-list id="unauthorized" *ngIf="!isAuthorized"></staff-list>
โดยถ้าค่า isAuthorized เป็น true ให้ <staff-list> ที่มี id เท่ากับ "authorized" แสดง
เป็น false ให้ซ่อน
กลับกัน <staff-list> ที่มี id เท่ากับ "unauthorized" ให้ทำงานตรงกันข้าม แสดงเมื่อ
ค่า isAuthorized เป็น false และซ่อนเมื่อ isAuthorized เป็น true
ซึ่งค่า isAuthorized จะมาจากคำสั่ง get isAuthorized() { return this.user.isAuthorized; }
ค่าจะเปลี่ยนแปลงเมื่อมีการกดปุ่มทำคำสั่ง nextUser()
ต่อไปเราจะไปดูที่ไฟล์ staff-list.component.ts เป็นไฟล์ที่แสดงรายชื่อ staff
และเราจะกำหนดให้เรียกใช้งาน factory provider ในนี้
ไฟล์ staff-list.component.ts
import { Component } from '@angular/core' import { Staff } from './staff' import { StaffService } from './staff.service' import { staffServiceProvider } from './staff.service.provider'; @Component({ moduleId: module.id, selector: 'staff-list', templateUrl: `./staff-list.component.html`, providers:[staffServiceProvider] }) export class StaffListComponent { staffs:Staff[]; constructor(private staffService:StaffService){ this.staffs = staffService.getStaffs(); } }
เรามีการ import provider object ที่ชือ staffServiceProvider เข้ามา แล้วนำมากำหนด
provider ใน @Component ของ StaffListComponent โดย provider จะทำงานทุกครั้ง
ที่ StaffListComponent ถูกเรียกใช้งาน ซึ่งก็คือ ถ้า <staff-list> ที่อยู่ในไฟล์ staff.component.ts
ตัวไหนแสดง StaffListComponent ก็จะถูกเรียกใช้งานพร้อมกับมีการใช้งาน provider ที่ import
เข้ามาด้วย
โดยเมื่อ StaffListComponent ถูกเรียกใช้งาน ก็จะทำงานคำสั่งใน constructor ทันทีซึ่งก็คือ
ใช้ StaffService class ผ่านตัวแปร staffService ทำงานคำสั่ง getStaffs() และเนื่องด้วยมีการใช้งาน
staffServiceProvider ทำให้ StaffService class เป้นตัวแปรที่ถูกสร้างใหม่โดยมีการส่งค่า isAuthorized
ที่มีการเปลี่ยนแปลงค่าตาม user ปัจจุบันในขณะนั้น เข้าไป ดังนั้น คำสั่ง getStaffs() ก็จะทำงาน
ตามเงื่อนไขค่า isAuthorized คือแสดงรายการ staff ทั้งหมดถ้า isAuthorized เป็น true และ
แสดงรายการเฉพาะ staff ที่มีอายุน้อยกว่า 30 ปี ถ้า isAuthorized เป็น false
ก่อนทดสอบผลลัพธ์ ให้เราไปทำการกำหนดค่าเพิ่มเติมในไฟล์ app.module.ts เนื่องจากเรามีการ
สร้าง component ขึ้นมาใหม่ชื่อ StaffComponent และมีการใช้นำเข้า UserService ด้วย
ให้แก้ไขไฟล์ app.module.ts ดังนี้
ไฟล์ app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { UserComponent } from './components/user.component'; import { StaffComponent } from './components/staff.component'; import { StaffFormComponent } from './components/staff-form.component'; import { StaffListComponent } from './components/staff-list.component'; import { Logger,objLogger } from './logger.service'; import { UserService } from './components/user.service'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, UserComponent, StaffComponent, StaffFormComponent, StaffListComponent ], providers:[ UserService, { provide: Logger, useClass: Logger } ], bootstrap: [ AppComponent ] }) export class AppModule { }
ให้เรา import StaffComponent และประการศใช้งาน class ใน declarations
และก็ import UserService พร้อมกับขึ้นทะเบียน provider ใน providers
จากนั้นทดสอบรัน App จะได้เป็น
ค่าเริ่มต้นเรากำหนด user เป็น Jubjang ซึ่งมีค่า isAuthorized เป็น false ทำให้แสดงรายชื่อ staff
เฉพาะที่มีอายุน้อยกวา 30 ปี จากนั้นพอเรากดที่ปุ่ม Next User เพื่อสลับ user ใหม่ ซึ่งจะสลับ
ไปเป็น Manup มี isAuthorized เป็น true ทำให้แสดงรายชื่อ staff ทั้งหมด ดังรูป
สัญลักษณะของ Dependency injection
Token หรือสัญลักษณะที่ระบุว่าสิ่งนั้นเป็น DI เราจะได้พบตลอดเมื่อมีการขึ้นทะเบียน provider โดยใน
ตัวอย่างที่ผ่านๆ มา ค่าของ provide จะเป็นชื่ออ้างอิงชื่อของ class ที่มีการใช้งาน
{ provide: Logger, useClass: Logger }
สมมติส่วนของการขึ้นทะเบียน provider เราจะเห็นว่า
provide ใช้คำว่า "Logger" ซึ่งเป็นชื่อของ class ที่เราใช้งาน ซึ่งเป็นสัญลักษณะของ DI
หรือที่เรียกว่า Token DI
ดังนั้น เวลาที่เรามีการขึ้นทะเบียน provider โดยใช้ service ที่เป็น class การกำหนดค่า
Token หรือชื่อตัวแปรอ้างอิง จะสะดวกตรงที่เราสามารถใช้ชื่อของ class มาใช้ได้เลย
แล้วกรณีที่เราต้องการใช้เป็น provider ไม่ใช้ class เราจะกำหนดค่า Token อย่างไร
เรามาดูกันในหัวข้อถัดไป
การใช้งาน provider ที่ไม่ใช่ class
ถ้าสิ่งที่จะนำเข้ามาใช้งานไม่ใช่ class เช่น บางครั้งเราต้องการจะเข้าเป้น String ,Function หรือ Object
เช่น App ของเราต้องการกำหนดการตั้งค่า เป็นข้อมูลบางอย่างที่จะใช้งาน เช่น ชื่อหัวเรื่องของ App
หรือที่อยู่ ของ web API เป้าหมาย แต่ค่าเหล่านี้ ไม่ได้อยู่ในรูปแบบของ class
ตัวอย่าง เช่น
export interface AppConfig { apiEndpoint: string; title: string; } export const STAFF_DI_CONFIG: AppConfig = { apiEndpoint: 'api.ninenik.com', title: 'Example DI' };
เราประกาศตัวแปร STAFF_DI_CONFIG ด้วยคำสั่ง const ซึ่ง การใช้ const ต่างจากการใช้คำสั่ง let
ตรง const ใช้กำหนดตัวแปรที่เป็นค่าคงที่ เปลียนแปลงไม่ได้
แต่ let ใช้กำหนดตัวแปร ที่เปลี่ยนแปลงค่าได้
จากโค้ดเรากำหนดตัวแปร STAFF_DI_CONFIG เป็น Object มี type เป็น AppConfig
ซึ่งเราใช้ interface คำสั่งที่มีใน TypeScript ในการกำหนด type ของ property Object อีกที
แล้วทีนี้ เราจะใช้ Object ไปใช้ในการกำหนด provider ได้อย่างไร ตรงนี้ให้เราย้อนไปดูในเรื่องการ
ใช้งาน Value provider เราจะพบว่าเราสามารถกำหนด Object ใน provider โดยใช้กำหนดค่าโดยใช้
useValue ตัวอย่าง
{ provide: Logger, useValue: objLogger }
จะเห็นว่า objLogger เป็น Object ที่เรานำมาใช้งานในการกำหนด Provider โดยใช้ useValue
โดยกำหนด provide หรือค่า Token เป็น ชื่อ class ชื่อ Logger ซึ่งเป็น class ที่มี Object ที่ชื่อ objLogger
อยู่ด้านใน
ไฟล์ logger.service.ts
import { Injectable } from '@angular/core'; import { Optional } from '@angular/core'; import { BestStaffService } from './components/beststaff.service'; @Injectable() export class Logger { logs: string[] = []; // กำหนดตัวแปร array แบบ string log(message: string) { // ฟังก์ชั่น log() this.logs.push(message); // รับค่าข้อความและเพิ่มไปใน array console.log(message); // แสดงข้อความใน console } } export let objLogger = { logs: ["ดึงข้อมูลโดยใช้ผ่าน 'useValue' "], log: () => { console.log("use method in objLogger"); } };
ดังนั้นกรณีการกำหนดของ objLogger Object จึงยังเป็นการกำหนด provider ที่เป็น class
ชื่อ Logger ที่มี Object objLogger อยู่ด้านใน
แล้วกรณีที่ไม่ใช่ class เราจะใช้ค่าไหน กำหนดเป็นค่า Token ค่าไหน
// FAIL! เราไม่สามารถใช้ interface เป็น provider token [{ provide: AppConfig, useValue: STAFF_DI_CONFIG })]
เนื่องจาก DI ไม่ได้เป็น class เราจึงไม่มีชื่อ class มากำหนด เป็น provider token ได้
วิธีแก้ปัญหา เราจะใช้ OpaqueToken class
หมายเหตุ: Angular ที่เราใช้งาน เป็นเวอร์ชั่น 2 OpaqueToken ยังสามารถใช้งานได้
แต่ถ้าเป็นเวอร์ชั่น 4 ปัจุบัน OpaqueToken จะถูกยกเลิก และให้ใช้ InjectionToken แทน
ในที่นี้เราจะอธิบายยึดตาม OpaqueToken ไปก่อน
การแก้ปัญหา กำหนด Provider Token ด้วย OpaqueToken
วิธีแก้ปัญหาการกำหนด Token ให้กับ provider ที่ไม่ใช้ class ให้เราสร้างไฟล์ชื่อ app.config.ts
ไว้ในไฟล์เดอร์ app
ไฟล์ app.config.ts
import { OpaqueToken } from '@angular/core'; export let APP_CONFIG = new OpaqueToken('app.config'); export interface AppConfig { apiEndpoint: string; title: string; } export const STAFF_DI_CONFIG: AppConfig = { apiEndpoint: 'api.ninenik.com', title: 'Example DI' };
เป็นการกำหนดให้ตัวแปร APP_CONFIG สามารถใช้กำหนดเป็น provider token ได้
โดยใช้คำส่ง new OpaqueToken('app.config');
app.config คือชื่อไฟล์ทีเรากำหนดให้ใช้งาน OpaqueToken
ต่อไปเราทำการใช้งาน Token ที่ได้โดยนำไปกำหนดในไฟล์ app.module.ts ดังนี้
ไฟล์ app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { UserComponent } from './components/user.component'; import { StaffComponent } from './components/staff.component'; import { StaffFormComponent } from './components/staff-form.component'; import { StaffListComponent } from './components/staff-list.component'; import { Logger,objLogger } from './logger.service'; import { UserService } from './components/user.service'; import { APP_CONFIG,STAFF_DI_CONFIG } from './app.config'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, UserComponent, StaffComponent, StaffFormComponent, StaffListComponent ], providers:[ UserService, { provide: Logger, useClass: Logger }, { provide: APP_CONFIG, useValue:STAFF_DI_CONFIG } ], bootstrap: [ AppComponent ] }) export class AppModule { }
ทำการ import APP_CONFIG และ STFF_DI_CONFIG เข้ามาใช้งาน
จากนั้นเราสามารถใช้ APP_CONFIG เป็น provider token กำหนดค่าลงไปใน provide
และใช้ STFF_DI_CONFIG เป็น Object ค่าที่จะใช้งาน
ทดสอบเรียกใช้งานในไฟล์ staff.component.ts
ไฟล์ staff.component.ts
import { Component,Inject } from '@angular/core'; import { UserService } from './user.service'; import { APP_CONFIG,AppConfig } from '../app.config'; @Component({ selector: 'staff', template: ` <br><h1>{{title}}</h1> <div class="container"> <button (click)="nextUser()" class="btn btn-sm btn-success"> Next User</button> </div> <staff-list id="authorized" *ngIf="isAuthorized"></staff-list> <staff-list id="unauthorized" *ngIf="!isAuthorized"></staff-list> `, }) export class StaffComponent { title:string; constructor( private userService:UserService, @Inject(APP_CONFIG) config: AppConfig ){ this.title = config.title; } get isAuthorized() { return this.user.isAuthorized; } nextUser() { this.userService.getNewUser(); } get user() { return this.userService.user; } }
เราทำการนำเข้ามาใช้งานใน constructor() กำหนดเป็น parameter ด้วยคำสั่ง
@Inject(APP_CONFIG) config: AppConfig
กำหนดตัวแปร config มี type ตาม interface AppConfig ใช้งานผ่าน APP_CONFIG provider token
จากนั้นเราก็สามารถอ้างอิงค่า ผ่านตัวแปร config อย่างตัวอย่าง
เรากำหนดให้ค่า title มีค่าเท่ากับ config.title จากนั้นทดสอบนำค่าไปแสงดงใน template
ผลลัพธ์ที่ได้จะเป็นดังนี้
ข้อความคำว่า Eample DI ซึ่งเป็นค่าที่เรากำหนดใน Object ที่ชื่อ STAFF_DI_CONFIG ถูกนำมาใช้งาน
ผ่านการกำหนดขึ้นทะเบียน provider ผ่านชื่อ APP_CONFIG provider token
ทั้งหมดคือเนื้อหาเกี่ยวกับ provider ที่เราได้ศึกษาเบื้องต้นถึงปานกลาง หากมีอัพเดทการใช้งานเพิ่ม
เติม จะนำมาอัพเดทต่อในบทความ ติดตามและแวะเข้ามาดูเป็นระยะๆ
ส่วนเนื้อหาต่อไป จะเป็นเกี่ยวกับอะไรนั้นรอติดตามต่อ