สำหรับใครที่พึ่งเริ่มต้นสามารถติดตามโค้ดได้ที่ Github

สำหรับในตอนนี้ เป็นบทเรียนสำหรับการสร้าง service ใช้สำหรับจัดการข้อมูลฮีโร่ เปลี่ยน hero ที่เป็นค่า const ไปเป็นการดึงค่ามาจากแหล่งข้อมูล

Why we need thie service?

Q: ทำไมถึงต้องเขียน service?
A: สมมติว่า ถ้าเราต้องการแสดงข้อมูลฮีโร่ของเราในหลายๆ component เราจะต้องทำยังไง? โดยปกติแล้วเราก็แค่ copy/paste โค้ดส่วนที่ใช้ในการดึงข้อมูลไปในแต่ละ component ก็ใช้การได้

แต่ถ้าวันหนึ่ง อยู่ๆ api ก็เปลี่ยนไป #ฉันไม่รักเธออีกต่อไป สิ่งที่เราต้องทำหลังจากนั้น คือการแก้โค้ดทุกๆที่เพื่อทำให้ทุกๆที่ที่ใช้ข้อมูลนี้กลับมาใช้การได้ และมันก็จะเป็นงานที่มีโอกาสทำให้เกิดบั๊คสูงมาก~

ซึ่ง service มีตัวตนมาเพื่อแก้ปัญหาเหล่านี้ เราจะใช้มันเป็นเป็นตัวกลางในการจัดการข้อมูลต่างๆ

Q: สภาพโค้ดตอนนี้เป็นยังไงบ้าง?
A: ตอนนี้เราเก็บข้อมูลทุกอย่างไว้ใน AppComponent และ Component อื่นๆไม่สามารถเข้าถึงข้อมูลนี้ได้ ToT

Create the HeroService

งานเร่งด้วนงานแรก คือ การสร้างไฟล์ hero.service.ts แล้วสร้าง service ตามโค้ดนี้

สิ่งที่เราทำ คือ สร้าง class โดยมี decorator เป็น @Injectable ซึ่งเราเห็นโค้ดรูปแบบนี้มาแล้วในการสร้าง HeroDetailComponent แต่ในบทนี้ /me จะอธิบายให้ละเอียดมากยิ่งขึ้น //อาจจะมั่วๆ เพราะไม่แน่ใจ 100%

ก่อนอื่น คือ การทำความเข้าใจกับ Decorator pattern ซึ่งเป็นรูปแบบหนึ่งสำหรับการเขียนแบบ OOP โดยเป้าหมายของ Decorator pattern คือ เพิ่มความสามารถบางอย่างให้กับ instance โดยที่เราไม่จำเป็นต้องแก้ไข class เดิม คล้ายๆกับการหุ้มเกราะเพิ่มอาวุธใหม่ให้กับ instance ~

เพื่อให้เข้าใจง่ายยิ่งขึ้น สามารถกดดูตัวอย่างด้านล่างนี้

อธิบายเพิ่มเติม
สมมติ เรากำลังจะชงกาแฟ โดยสร้าง interface ของเมนูกาแฟทุกๆเมนู จะต้องประกอบไปด้วย
* getCost()
* getIngredients()

และมี class SimpleCoffee สำหรับกาแฟดำธรรมดาๆ

จากนั้นก็สร้าง CoffeeDecorator 2 ตัว คือ WithMilk และ WithWhippingCream

โดยแต่ละ decorator คือ การใส่เพิ่มออพชั่น เข้าไปใน instance ทำให้ instance มีพฤติกรรมเปลี่ยนแปลงไป

เมื่อเราสร้าง Coffee a มาแล้ว เราสามารถเพิ่ม decorator เข้าไปเท่าไรก็ได้ แต่โดยที่มันยังคงมีความเป็น object Coffee เหมือนเดิม แต่มีมูลค่าเพิ่มขึ้น อ่านรายละเอียดเพิ่มเติมที่นี้

จริงแล้วจะพบว่า การใช้ decorator แอบมีความสามารถคล้ายๆกับการทำ subclassing แต่มีความยืดหยุ่นมากกว่า

ฟีเจอร์ Decorator นี้จริงๆเป็นแค่แนวคิดที่ยังไม่ได้ approve เข้ามาเป็นส่วนหนึ่งของ ES6 แต่สามารถใช้ได้ใน typescript นี้จึงเป็นเหตุผลหนึ่งที่ทำให้ angular สนับสนุนให้ใช้ typescript

ในกรณีของ HeroService
เมื่อเรากำหนดให้ HeroService เป็น @Injectable ทำให้ HeroService เป็น class ที่สามารถ inject เอา module อื่นๆมาใช้ได้ ถึงแม้ว่าตอนนี้เรายังไม่จำเป็นต้อง inject ก็ตาม

ต่อมาคือการสร้างแหล่งข้อมูลเพื่อให้ HeroService ใช้ในการดึงข้อมูล โดยอาจจะเป็น database หรือ ส่ง request เพื่อดึงข้อมูลจาก api แต่ในที่นี้จะ mock up โดยการย้ายข้อมูล HEROES จาก AppComponent แล้วเอาไปใส่ที่ app/mock-heroes.ts

จากนั้นก็กลับมาที่ HeroService แล้วกำหนด getHeros() ให้ return HEROES ที่ mock up เอาไว้

Inject the HeroService

แน่นอนว่า ตอนนี้เราพร้อมสำหรับการใช้ HeroService โดยการ new instance ของ HeroService ขึ้นมา

แต่เพราะว่า

* ถ้าเราเปลี่ยน HeroService constructor แปลว่าเราจำเป็นต้องไล่แก้ทุกไฟล์ที่มีการเรียกใช้ HeroService และมันยังทำให้เกิดปัญหาวุ่นวาย เมื่อเราต้องการเขียน test

* ทุกๆครั้งที่ new HeroService จะเกิด instance ซึ่งแต่ละ Component จะได้ต่าง instance กัน และไม่แชร์ state ของกันละกัน และจะเป็นเรื่องยากมากสำหรับการสร้าง service caches และ global storage

เพื่อแก้ปัญหาพวกนี้ เราจะมาพึ่งสิ่งที่เรียกว่า “Dependency Injection”

เริ่มต้นด้วยการ นิยาม consturctor ให้กับ AppComponent และ กำหนด provider

เมื่อมีการสร้าง instance AppComponent ขึ้นมา angular จะเข้าไปดูลิส providers แล้ว inject service ที่ถูกต้องให้ทันที

เมื่อเรา inject HeroService มาให้ AppComponent แล้ว งานต่อมาจึงกลายมาเป็นการเรียกใช้ getHeroes() ที่เรานิยามเอาไว้

ดูเหมือนว่า ทุกอย่างจะเป็นไปตามที่เราต้องการ แต่!!

ความจริงแล้ว เราไม่ควรเขียนคำสั่ง/logic วุ่นวายๆ ภายใน constructor ในกรณีนี้ มันอาจจะเป็นแค่การเรียก 1 service แต่ถ้าต่อไปเราจำเป็นต้องใช้หลายๆ service เราจะทำยังไง?

The ngOnInit lifecycle hook

ภายใน constructor มักจะปล่อยไว้ใช้สำหรับการ binding ตัวแปรต่างๆจากภายนอกมาเป็น instance variables

ดังนั้น เราจะเปลี่ยนมาใช้ “Lifecycle Hooks” แทนการเรียกใน constructor

โดย Lifecycle คือ event handler function ที่ angular จะเรียกขึ้นมาเมื่อเกิด event บางอย่างกับ Component เช่น init, destroy, change

สำหรับในกรณีนี้ เราจะใช้ interface OnInit เพื่อกำหนด ngOnInit() แทนการเรียก getHeroes() ใน constructor

Async services and Promises

ปัญหาสำคัญจริงๆที่จะเกิดขึ้นเมื่อเราใช้ service ดึงข้อมูลจาก database หรือ api คือ ระบบพวกนั้นไม่สามารถให้คำตอบได้อย่างทันทีทันใด เราจึงต้องใช้ asynchronous technique ที่มีแฝงอยู่ใน javascript

แต่มีมาตรฐานหนึ่งที่ใช้สำหรับสร้าง asynchronous service เรียกว่า Promise โดยจะเป็น object กลางในการจัดการ state ของข้อมูล เมื่อข้อมูลส่งมาครบท้วนสมบูรณ์แล้ว promise instance จะเรียก resolve function แต่ในทางกลับกันข้อมูลมีปัญหามันจะเรียก reject function

เพื่อจำลองเหตุการณ์ที่น่าจะเกิดขึ้นเราจะเปลี่ยน HeroService ไปเป็นการ return Promise แทนที่จะ return HEROES ตรงๆ

และ หลังจาก HeroService เปลี่ยนไปใช้ Promise แล้ว เราจำเป็นต้องไปแก้ AppComponent เพื่อให้รองรับการใช้ Promise โดย promise instance นี้จะมี function ที่ชื่อว่า then(successCallback, failCallback) รับ parameters เป็น function 2 ตัว โดยตัวแรกจะโดยรันเมื่อ resolve() และทางกลับกันตัวที่ 2 จะโดยเรียกเมื่อ reject()

สามารถลบความเยิ่นเย้อ โดยใช้ Arror function ได้ดังนี้

ในตอนนี้ก็เป็นอีกบทสำหรับการ refactoring และอาจจะดูหนักหน่วงไปหน่อย เพราะต้องเจอคำศัพย์ใหม่ๆหลายๆตัว ไม่ว่าจะเป็น Service, Decorator, Dependency Injection และ Promise

แต่มันเป็นเรื่องสำคัญที่จะใช้ในระบบจริงๆ

จบตอน~

หนทางสำหรับ tutorial นี้ยังเหลืออีกแค่ไม่กี่ก้าว สู้ๆ

PS.

เนื้อหาทั้งหมด จะแบ่งเป็น 7 ตอน
EP1. introduction
EP2. The Hero Editor
EP3. Master/Detail
EP4. Multiple Components
EP5. Services
EP6. Routing
EP7. HTTP
ตามดูโค้ดได้ที่ Github

Thank you

Angular.io บทความนี้อยู่ภายใต้ลิขสิทธิ์แบบ CC BY 4.0

Tell your friend about thisShare on Facebook
Facebook
0Tweet about this on Twitter
Twitter
Share on Google+
Google+
0