อะไรคือ Dependency Injection ใน Angular 4

Supagrid Tangsermsit
Angular in Thailand
3 min readOct 23, 2017

สำหรับคนที่เริ่มเขียน Angular 2 หรือ 4 หรือ 5 ก็คงจะได้เห็นและได้ยินคำว่า Dependency Injection แน่นอน ซึ่งถือว่าเป็นหนึ่งในหัวใจหลักของ Angular ตั้งแต่ Angular 1 เลยก็ว่าได้ แล้วมันคืออะไรละมาลองทำความเข้าใจไปด้วยกัน ในบนความนี้ผมจะใช้ DI แทน Dependency Injection นะครับ

Dependency Injection หรือ DI คืออะไร

DI เป็นเทคนิคหนึ่งในการ ป้องกันการสร้าง (class หรืออะไรก็ตาม) บ้างอย่างด้วยตัวเอง และจะใช้วิธีการรับสิ่งที่ต้องการเข้ามาผ่านทางใดทางหนึ่ง ( constructor ของ class ก็ใช้ ) สำหรับ DI ของ Angular เอง ได้ให้เหตุผลว่าทำไมต้องใช้ DI มา 3 ข้อ

  1. brittle (เปราะบาง)
  2. inflexible (ไม่ยืดหยุ่น)
  3. hard to test (ยากต่อการเขียน test)

ทำถึงต้อง 3 ข้อนี้ละลองไปดูกัน

คลาส Notebook สร้างทุกอย่างที่จำเป็นสำหรับ Notebook ตัวอย่างโค้ดด้านบน Notebook จะสร้าง mainboard และ harddisk เมื่อมีการประกาศ new Notebook() ขึ้นมา ปัญหาที่จะพบเจอในคลาสนี้มีดังนี้

  1. brittle (เปราะบาง) : เมื่อคลาส Mainboard หรือ Harddisk ต้องการ parameter เพิ่มเติมละ? คลาส Notebook พังแน่นอน
  2. inflexible (ไม่ยืดหยุ่น) : ถ้าหากต้องการเปลี่ยนชนิดของ Mainboard หรือ Harddisk ละ? หรือถ้า Notebook บ้างคลาสต้องการ Mainboard หรือ Harddisk ที่ไม่เหมือนกันละ?
  3. hard to test (ยากต่อการเขียน test) : เราไม่สามารถคาดเดากับสิ่งที่จะเกิดขึ้นในคลาส Notebook ได้เลย เนื่องจากเรามี dependency กับ Mainboard และ Harddisk ที่เราไม่สามารถควบคุม 2 คลาสนี้ได้

สิ่งที่ได้กล่าวถึงมานี้สามารถแก้ได้ด้วยการเขียนคลาสแบบ DI อย่างเช่น

จากตัวอย่างโค้ดด้านบน เห็นได้ว่าเราจะส่งทุกอย่างเข้าไปใน constructor ของคลาส Notebook จาก 3 ข้อสังเกตุที่ได้กล่าวมา สามารแก้ปัญหาของการ brittle ของคลาส Notebook ได้

ทีนี้เราไม่ต้องกังวลแล้วว่าจะมีการเพิ่ม parameter เข้ามาก็จะไม่มีผลกับคลาส Notebook แต่มีผลกับคลาสอื่นแทน

จากโค้ดข้างบนเราสามารถเปลี่ยนชนิดของ Mainboard ได้ง่าย ๆ และไม่ผูกมัดกับอะไรเลยที่อยู่ภายในคลาส Notebook ซึ่งทำให้สิ่งนี้ inflexible มากขึ้นอย่างเห็นได้ชัดเลย

จาก 2 ข้อที่ได้กล่าวมา เราสามารถ Mock คลาส Mainboard และ Harddisk ทีนี้เราจะได้ไม่ต้องกังวลของที่จะเข้าสู้คลาส Notebook ของเราได้ ทำให้เราสามารถเขียน Test ได้ตามที่เราคาดหวัง

แล้ว Dependency Injection ของ Angular ละ

เรามาดูวิธีใช้กันก่อนดีกว่า

ตอนนีี้เรามี CatService สำหรับนำมาใช้แล้ว ซึ่งเราสามารถกำหนดการเรีกใช้ได้ที่ component โดยกำหนด metadata ชื่อ providers แล้วใส่ CatService เอาไว้ข้างในนั้น

อีกหนึ่งทางเลือกสำหรับการกำหนด service ที่จะใช่คือเพิ่งลงใน Module ของ Component เลย

ความแตกต่างของทั้ง 2 อย่างคืออะไรละ? เดี๋ยวมาดูกันต่อจากนี้

มาทำความเข้าใจกันก่อน

การที่เราจะใช้ DI เราต้องมีการลงทะเบียนเพื่อระบุ dependency และเราจะได้สิ่งที่ไว้ระบุตัวตนคือ token โดยเจ้า token นี้แหละที่เอาไว้ระบุสถานที่ รวมถึง instance ของ object นั้น ๆ ด้วย

ระบบ Dependency injection ใน Angular ประกอบกันเป็น 3 ส่วนคือ

  1. Provider สำหรับแมป Token จะเป็น string หรือ class ก็ได้ จาก dependencies ซึ่งเป็นตัวบอกวิธีการสร้าง object และจะให้ token มา
  2. Injector เป็นส่วนที่สร้าง เก็บ และให้ instance ของ object ที่ถูก inject ให้แก่เรา
  3. Dependency สิ่งที่จะเกิดขึ้นเมื่อมีการ inject

Dependency Injection Framework

Angular มี DI framework เป็นของตัวเองเพื่อเอาไว้ใช้ใน Angular 4 หรือ project ที่ต้องการใช้ เช่นตัวอย่างข้างล่าง

Singleton Services

การใช้ Dependency Injection จะเป็น Singleton อยู่แล้ว แต่ต้องภายใน Scope (providers)ที่เราได้ประการใช้งาน dependency เอาไว้

ตัวอย่างการใช้ DI

จากตัวอย่างด้านบนเราประกาศ CatService ไว้ที่ NgModule ทำเมื่อเรากดที่ปุ่ม cat จะเป็นการเพิ่ม count ของ CatService ที่เก็บไว้โดยทั้ง CatComponent และ DogComponent จะแสดงจำนวน count ที่เท่ากันเสมอ แล้วถ้าเราเอา CatService ของ เอาใส่ไว้ใน CatComponent และ DogComonent ละ ที่นี้ไว้ว่าจะกดที่ปุ่ม cat กี่ครั้งก็จะเพิ่มแค่ CatComponent เท่านั้น ทำไมละ?

เพราะเราได้ประกาศ provider ของ CatComponent กับ DogComponent แยกกัน ซึ่งจะเป็นการสร้าง dependency กันคนละที่ token ทั้ง 2 อันก็จะต่างกันด้วย ฉะนั้นระวังไว้ด้วยว่าอาจจะประกาศไม่ตรงตามที่เราต้องการก็ได้

เพิ่มเติม

@Optional() เราสามารถบอกว่าสิ่งที่จะ Inject เข้ามาเป็น Optional ซึ่งถ้าเราไม่ได้ประกาศ CatService เอาไว้แล้วไม่ได้บอกว่า Optional ก็จะ error “No provider for CatService!” ถ้าบอกว่าเป็น Optional ก็ไม่ได้ต้องเจอ error นั้น

@Self(): ประกาศให้ injector ส่ง dependency เฉพาะของตัวเองเท่านั้น

@Host(): ประกาศให้ injector ส่ง dependency ที่เป็น Host ของ Component

@SkipSelf(): ประกาศให้ injector ส่ง dependency ที่ใช่ของตัวเอง ถ้า host ไม่ได้ประกาศเอาไว้ก็จะ error “No provider

โดยปกติเรามารถประกาศ providers โดยการประกาศชื่ีอของ service ลงใน metadata providers ทั้งใน Component หรือ NgModule

เราสมารถประกาศอีกแบบนึงได้ โดยเราใช้ userClass เพื่อระบุ class ที่ใช้ และ provide อาจเป็น class หรือ string ก็ได้

ตัวอย่างด้านล่างเป็นการระบุว่าให้ใช้ class Cat2Service และมี provide แทน Cat2Serviceได้ทันที

นอกจาก useClass เราสามารถใช ้userValue, useExisting และ useFactory เพื่อสามารถ Inject โดยที่ไม่ต้องเป็น class ก็ได้

สรุป

DI ถือว่าเป็นหัวใจหลักของ Angular ซึ่งเราสามารถนำระบบของ DI มาใช้ได้โดยที่ไม่ต้องพึง Angular 2 เลยก็ได้ และยังช่วยให้เราไม่ต้องมาจัดการเรื่อง instance ที่เยอะแยะมากในการจัดการด้วยการทำเป็น Singleton ให้เราเองเลย

--

--

Supagrid Tangsermsit
Angular in Thailand

Frontend Developer ผู้ใฝ่ฝันอยากทำร้านขนมปัง