ภาพรวมการเชื่อมโยงข้อมูล
การเชื่อมโยงข้อมูลเป็นกลไกผสานระหว่าง สิ่งที่ user เห็นกับค่าของข้อมูลใน App ในขณะที่เรา
ทำการส่งข้อมูลเข้ามาหรือดึงข้อมูลออกไปจาก HTML การเขียน การอ่าน และการดูแล App จะเป็น
ไปอย่างง่ายดาย ด้วยการใช้รูปแบบโครงสร้างการเชื่อมโยงข้อมูล (binding framework) โดยเราเพียง
แค่ประกาศการเชื่อมโยงข้อมูลระหว่างแหล่งข้อมูล กับ HTML element เป้าหมาย แล้วปล่อยให้ framework
ทำงาน
Angular เตรียมรูปแบบการเชื่อมโยงข้อมูลไว้ให้หลายรูปแบบ แนวทางต่อไปนี้จะครอบคลุมรูปแบบส่วนใหญ่
เหล่านั้น
ประเภทของการเชื่อมโยงข้อมูล สามารถจัดกลุ่มได้เป็น 3 หมวดหมู่เด่นๆ ตามทิศทางการเคลื่อนของข้อมูล
ได้แก่ จากแหล่งข้อมูลไปยังหน้าแสดงผล , จากหน้าแสดงผลมายังแหล่งข้อมูล และในทิศทางไปกลับหรือสอง
รูปแบบแรกรวมกัน คือทั้งจากแหล่งข้อมูลไปยังหน้าแสดงผลและจากหน้าแสดงผลไปยังแหล่งข้อมูล
ตารางรูปแบบการเชื่อมโยงข้อมูล
ทิศทางการไหล ของข้อมูล |
ไวยกรณ์ | ประเภทการเชื่อมโยง |
---|---|---|
One-way จากแหล่งข้อมูล ไปยังหน้าแสดงผล |
{{expression}} |
Interpolation Property Attribute Class Style |
One-way จากหน้าแสดงผล ไปยังแหล่งข้อมูล |
(target)="statement" |
Event |
Two-way ส่งค่ากลับไปมา ระหว่างหน้าแสดงผล และแหล่งข้อมูล |
[(target)]="expression" |
Two-way |
ประเภทการเชื่อมโยงข้อมูลที่นอกเหลือจากการแทรกค่าตัวแปร (Interpolation) จะมี target name อยู่ด้านซ้าย
ของเครื่องหมาย ( = ) ล้อมรอบด้วยเครื่องหมายวรรคตอนปีกาสี่เหลี่ยมหรือวงเล็บ หรือขึ้นต้นด้วยการใช้คำนำหน้า
(bind-, on- , bindon-)
ชื่อของ target ก็คือชื่อของ property ในบางครั้งดูเหมือนกับชื่อของ attribute แต่ไม่ใช่ การตระหนักถึงข้อแตกต่าง
เราจะต้องพัฒนาความเข้าใจเกี่ยวกับ template HTML ใหม่
โมเดลความคิดใหม่
ด้วยความสามารถของการเชื่อมโยงข้อมูลและความสามารถในขยายศัพท์ของ HTML ให้กว้างออกด้วยการใช้รูปแบบ
โครงสร้าง tag ที่สามารถกำหนดเอง ทำให้เรารู้สึกเหมือนกับว่า template HTML ก็คือ HTML plus (HTML+)
เหมือนกับเป็น HTML ที่เหนือกว่า HTML ปกติทั่วไป
ในวิถีทางการพัฒนาด้วย HTML ปกติทั่วไป เราสร้างโครงสร้างที่เป็นรูปร่างด้วยการใช้ HTML element และแก้ไข
element นั้นๆ ด้วยการกำหนดค่า "attribute" ของ element ด้วยข้อความค่าคงที่
<div class="special">Mental Model</div> <img src="images/staff.png"> <button disabled>Save</button>
เรายังคงสร้างโครงสร้างและกำหนดค่าเริ่มต้นของ "attribute" ตามแนวทางนี้ใน Angular teplate
จากนั้นเราก็ได้เรียนรู้ การสร้าง element ใหม่ ด้วยการใช้งาน component ที่ครอบด้วย HTML
แล้วนำไปวางใช้งานใน template เหมือนกับ HTML element ปกติ
<!-- Normal HTML --> <div class="special">Mental Model</div> <!-- A new element! --> <staff-detail></staff-detail>
และนี่คือรูปแบบของ HTML plus
ต่อมาเราได้เรียนรู้เกี่ยวกับการเชื่อมโยงข้อมูล ในลักษณะต่อไปนี้
<!-- เชื่อมโยงสถานะ disabled ของปุ่ม เป็นค่า`isUnchanged` ใน component property --> <button [disabled]="isUnchanged">Save</button>
เราจะพบลักษณะการใช้ปีกกาสี่เหลี่ยม [] เป็นสัญลักษณ์ เรารู้ได้ว่ากำลังเชื่อมโยงกับ "disabled" attribute
ของปุ่ม button และกำหนดค่าปัจจุบันเป็นให้เท่ากับค่าของ property ที่ชื่อ isUnchanged จาก component
ซึ่งเป็นความเข้าใจที่ไม่ถูกต้อง แนวความคิดเกี่ยวกับ HTML ที่ใช้อยู่ทุกๆ วันทำให้เราเข้าใจผิด ซึ่งจริงๆ แล้ว
เมื่อมีการเชื่อมโยงกับข้อมูล เราไม่ได้ทำงานใดๆ กับ HTML attribute ไม่ได้มีการกำหนดค่าให้กับ attribute
แต่เป็นการกำหนดค่าให้กับ property ของ DOM element, component หรือ directive
ทำความรู้จัก HTML attribute กับ DOM property
ความแตกต่างระหว่าง HTML attribute และ DOM property คือสิ่งสำคัญในการทำความเข้าใจ
เกี่ยวกับการเชื่อมโยงข้อมูลใน Angular ว่าทำงานอย่างไร
Attribute ถูกกำหนดด้วย HTML ส่วน Property ถูกกำหนดด้วย DOM (Document Object Model)
- มี HTML attribute ไม่กี่อันที่จับคู่ 1:1 กันกับ property หรือหมายถึงมีชื่อตรงกัน เช่น id
- ค่า HTML attribute บางค่า ที่ไม่สอดคล้องกับค่าของ property หรือหมายถึงไม่มีในค่า property เช่น colspan
- ค่า DOM property บางค่า ที่ไม่สอดคล้องกับค่าของ attribute หรือหมายถึงไม่มีในค่า attribute เช่น textContent
- มีหลายๆ HTML attribute ที่ชื่อตรงกับ property แต่กลับมีค่าหรือทำหน้าที่ไม่เหมือนกัน
เราอาจสับสนในหัวข้อสุดท้ายที่ว่า มีชื่อตรงกันแต่ทำไมมีค่าหรือทำหน้าที่ไม่เหมือนกัน ดังนั้นเรามาทำความ
เข้าใจกฎทั่วไป คือ
Attribute จะทำการกำหนดค่าเริ่มต้นให้กับ DOM property เท่านั้น โดยที่ค่า property สามารถเปลี่ยนแปลงค่าได้
ในขณะที่ attribute ไม่สามารถเปลี่ยนแปลงค่า
ยกตัวอย่าง เมื่อเราเปิดหน้าเพจผ่านบราวเซอร์ และทำการแสดง tag
<input type="text" value="Jubjang">
ก็จะมีการสร้าง DOM node ที่มีความสอดคล้องกับค่าของ property ที่ชื่อ "value" โดยให้ค่าเริ่มต้นของ "value" property
เท่ากับ "Jubjang"
และเมื่อ user ทำการกรอกข้อมูลเข้าไปใหม่คำว่า "Manop" ค่าของ "value" property ของ DOM element ก็จะเปลี่ยน
เป็น "Manop" แต่ HTML "value" attribute จะยังคงเป็นค่าเดิมคือ "Jubjang" ไม่เปลี่ยนแปลงตาม
สมมติหน้าเพจเรามีโค้ดดังตัวอย่างด้านบน
// เราสามารถเรียกดูค่า "value" property ของ DOM ได้จากคำสั่ง ผ่าน console ในบราวเซอร์ document.getElementsByTagName('input')[0].value // จะได้ค่า "Jubjang"
ดูเพิ่มเติมเกี่ยวกับ DOM Object ได้ที่ https://www.w3schools.com/jsref/dom_obj_document.asp
// จากนั้นเราลองเปลี่ยนค่าใน input box เป็น "Manop" แล้วใช้คำสั่งเดิม ดูค่า "value" property ของ DOM // และใช้คำสั่ง getAttribute('value') เรียกดูค่า "value" attribute document.getElementsByTagName('input')[0].value // จะได้ค่า "Manop" document.getElementsByTagName('input')[0].getAttribute('value') // จะได้ค่า "Jubjang"
กล่าวคือค่าของ HTML attribute หมายถึงค่าเริ่มต้นเปลี่ยนแปลงไม่ได้ ในขณะที่ DOM property จะหมายถึงค่าปัจจุบัน
อีกตัวอย่างที่มีลักษณะเฉพาะ ก็คือการใช้งาน "disabled" attribute
<button>Enable</button>.
ค่าเริ่มต้นของ "disabled" property ในปุ่ม button คือ false แม้ว่าเราไม่ได้กำหนดลงไปก็ตาม
document.getElementsByTagName('button')[0].disabled // ได้ค่าเป็น false
เราลองเพิ่ม "disabled" attribute เข้าไป เป็น
<button disabled>Enable</button>
ปรากฏว่ามีการกำหนดค่าเริ่มต้นให้กับ "disabled" property มีค่าเป็น true
ซึ่งปุ่มจะกลายเป็น disabled
document.getElementsByTagName('button')[0].disabled // ได้ค่าเป็น true
การมีหรือไม่มี "disabled" attribute มีผลต่อสถานะของปุ่มว่าเป็น disable หรือ enable
แต่การกำหนดค่าของ attribute กลับไม่มีผลสอดคล้องสัมพันธ์กัน เช่นเมื่อเราเพิ่มค่าของ "disabled" attribute
เป็น false
<button disabled="false">Still Disabled</button>
แทนที่ปุ่มจะมีสถานะเป็น enabled แต่กลับยังเป็น disabled อยู่
และเมื่อเราตรวจสอบค่า "disabled" property กลับมีความสอดคล้องสัมพันธ์
กว่าการใช้งาน "disabled" attribute
document.getElementsByTagName('button')[0].disabled // ได้ค่าเป็น true
จะเห็นว่า HTML attribute และ DOM property ไม่ใช่สิ่งเดียวกัน ถึงแม้จะใช้ชื่อเหมือนกันก็ตาม
นี่จึงเป็นเหตุผลว่าทำไม Angular จึงให้ความสำคัญกับ DOM property มากกว่า HTML attribute
ในการใช้เชื่อมโยงข้อมูล
ในการใช้งาน Angular กฎเดียวเกี่ยวกับ attribute คือใช้สำหรับกำหนดค่าเริ่มต้นให้กับ element หรือกำหนด
สถานะของ directive แต่เมื่อใดก็ตามที่มีการเชื่อมโยงข้อมูล ก็จะเป็นการติดต่อกับ property และ event ของ
object เป้าหมายเท่านั้น
เป้าหมายการเชื่อมโยง Binding targets
เป้าหมายการเชื่อมโยงข้อมูล เป็นสิ่งหนึ่งที่อยู่ใน DOM ผันแปรไปตามประเภทของการเชื่อมโยง สามารถเป็นได้
ทั้ง property ของ (element , component หรือ directive) หรือ event ของ (element , component หรือ directive)
หรืออาจจะมีบ้างที่เป็นชื่อของ attribute ดูตารางสรูปด้านล่างประกอบ
Type | Target | ตัวอยาง |
---|---|---|
Property | Element property Component property Directive property |
<img [src]="staffImageUrl"> |
Event | Element event Component event Directive event |
<button (click)="onSave()">Save</button> |
Two-way | Event and property | <input [(ngModel)]="name"> |
Attribute | Attribute (the exception) | <button [attr.aria-label]="help">help</button> |
Class | class property | <div [class.special]="isSpecial">Special</div> |
Style | style property | <button [style.color]="isSpecial ? 'red' : 'green'"> |
จากตารางข้างต้น เรามาดูรายละเอียดการเชื่อมโยงของแต่ละประเภทกัน
การเชื่อมโยงด้วย property - Property binding ( [property] )
เราใช้การเชื่อมโยงด้วย property ในการกำหนดค่า property ของ element เชื่อมโยงกับค่า นิพจน์ของ template
โดยส่วนใหญ่ที่เราเจอผ่านๆ มา ก็คือเชื่อมโยงกับค่าของ component property ตัวอย่างเช่น
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` <img [src]="staffImageUrl"> <button [disabled]="isUnchanged">Cancel is disabled</button> <div [ngClass]="classes">[ngClass] binding to the classes property</div> `, }) export class AppComponent { staffImageUrl:string; classes:string; isUnchanged:boolean; constructor(){ this.staffImageUrl = 'staff.png'; this.classes = 'mycssclass'; this.isUnchanged = true; } }
การเชื่อมโยงค่า "src" property ของ image element กับ ค่า component property ที่ชื่อ staffImageUrl
<img [src]="staffImageUrl">
หรือตัวอย่าง การกำหนดให้ปุ่มมีสถานะเป็น disabled โดยการกำหนดค่า "disabled" property เชื่อมโยงกับค่า
isUnchanged ที่มีค่าเท่ากับ true ใน component property
<button [disabled]="isUnchanged">Cancel is disabled</button>
หรือตัวอย่างการค่า property ของ ngClass ซึ่งเป็นชื่อ directive มีค่าเท่ากับ component property ที่ชื่อ classes
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
หรือนอกเหนือจากตัวอย่างข้างต้น เช่น การกำหนดค่าให้กับ model property ของ component ที่เราสร้างขึ้นมาเอง
ดังตัวอย่าง
<staff-detail [staff]="currentStaff"></staff-detail>
ทิศทางการไหลของข้อมูลของการเชื่อมโยงแบบ propery binding
เป็นไปในลักษณะ One-way in คือการไหลของค่าข้อมูลไปในทิศทางเดียวจากข้อมูลของ component property
มายัง property ของ element เป้าหมาย
เราไม่สามารถใช้งาน property binding ในการดึงค่าออกจาก element เป้าหมาย
ไม่สามารถอ่านค่า propery ของ element เป้าหมาย
เราสามารถทำได้เพียงกำหนดค่า property ให้กับ element เป้าหมายเท่านั้น
ลักษณะคล้ายๆ กับเราไม่สามารถใช้ property binding ในการเรียกใช้คำสั่ง ใน element เป้าหมายได้
การเรียกใช้คำสั่งใน element เป้าหมาย เราจะใช้เป็นแบบ event binding แทน ซึ่งอยู่ในหัวข้อต่อๆ ไป
เป้าหมายการเชื่อมโยง Binding target
property ของ element ที่อยู่ในเครื่องหมายปีกกาสี่เหลี่ยม เป็นชื่อที่ระบุถึง property เป้าหมาย
อย่าง property เป้าหมายของโค้ดด้านล่างคือ "src" property
<img [src]="staffImageUrl">
เราสามารถกำหนดโดยใช้ "bind-" นำหน้าชื่อของ property เพื่อกำหนดในลักษณะนี้แทนได้
<img bind-src="staffImageUrl">
ชื่อของ target หรือเป้าหมายจะเป็นชื่อของ property เสมอ แม้ว่าจะดูเหมือนเป็นชื่อของสิ่งอื่น ของ "src"
เราอาจจะคิดว่าเป็นชื่อของ attribute แต่จริงๆ ไม่ใช่ มันเป็นชื่อ property ของ image element
ทั่วไปบางทีแล้ว element property จะเป็น target แต่ Angular จะดูที่ property ของ directive ก่อนถ้ามี
อย่างตัวอย่างเช่น ngClass ซึ่งเป็น property ของ directive
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
อย่างไรก็ตาม ถ้าเรากำหนดชื่อของ property ผิดพลาด Angular จะแจ้ง error ว่า “unknown directive”
การเลี่ยงผลกระทบ
อย่างที่ได้กล่าวมาแล้วก่อนหน้าว่า การประมวลผลนิพจน์ของ template ต้องไม่มีผลกระทบข้างเคียงใดๆ เกิดขึ้น
อย่างเช่นการไปกำหนดนิพจน์ในรูปแบบต้องห้ามอย่างการใช้เครื่องหมาย ++ หรือ --
เราสามารถเรียกใช้คำสั่งในลักษณะ getFoo() ในกรณีที่เราแน่ใจหรือทราบว่าคำสั่ง getFoo() นั้นทำหน้าที่อะไร
ถ้าคำสั่ง getFoo() ทำการเปลี่ยนแปลงบางสิ่งแล้วเราทำการเชื่อมโยงกับสิ่งนั้น เราอาจจะเสี่ยงต่อประสบการณ์
การใช้งานที่ไม่น่าประทับใจ Angular อาจจะแสดงหรือไม่แสดงค่าที่เปลี่ยนแปลงนั้นๆ ได้ หรือ Angular อาจจะ
ตรวจพบการเปลี่ยนแปลงและแจ้ง error ออกมาแทน ดังนั้นโดยทั่วไปแล้ว เราควรยึดกับค่าของ property
และคำสั่งที่มีการคืนค่ากลับมาเท่านั้น ไม่ควรทำคำสั่งอย่างอื่น
การคืนค่าข้อมูลที่ถูกต้องเหมาะสม
การใช้งานนิพจน์ของ template ควรประมวลผลให้ได้ค่าที่เป็นไปตามค่าที่ target property ต้องการ
อย่างคืนค่าเป็น string ถ้า property นั้นต้องค่า string หรือคืนค่าเป็น number ถ้า property นั้นต้องการค่า
ที่เป็น number หรือแม้แต่การคืนค่าเป็น object ถ้า property นั้นต้องการค่าที่เป็น object
ยกตัวอย่างเช่น "staff" property ของ StaffDetail component ต้องการ "staff" object
ดังนั้นค่า currentStaff ก็ต้องเป็น object แบบนี้เป็นต้น
<staff-detail [staff]="currentStaff"></staff-detail>
จำไว้ว่าต้องใช้ปีกกาสี่เหลี่ยม
เครื่องหมายปีกกาสี่เหลี่ยน จะบอกให้ Angular ทำการประมวลผลค่านิพจน์ของ template ถ้าเราไม่ได้กำหนด
ปีกกาสี่เหลี่ยม Angular จะถือว่าค่านั้นเป็นข้อความ string ค่าคงที่ และกำหนดค่าเริ่มต้นเป็นค่าของข้อความนั้น
โดยไม่ทำการประมวลผลข้อความ string นั้น ตัวอย่างกรณีลืมกำหนดเครื่องหมายปีกกาสี่เหลี่ยม
<!-- ERROR: StffDetailComponent.staff ต้องการค่า Staff object, not the string "currentStaff" --> <staff-detail staff="currentStaff"></staff-detail>
การกำหนดข้อความค่าเริ่มต้น
เราสามารถกำหนด property โดยไม่ใช้เครื่องหมายปีกกาสี่เหลี่ยมในกรณีเป็นไปตามเงื่อนไขต่อไปนี้
- property นั้นต้องการค่าที่เป็น string
- ค่าของ string เป็นค่าคงที่
- ค่าเริ่มต้นนี้จะไม่มีการเปลี่ยนแปลง
ในมาตรฐาน HTML ทั่วไปเรากำหนดค่าเริ่มต้นของ attribute ในรูปแบบนี้ และมันทำงานได้ดีในการใข้งาน
กับ directive และค่าเริ่มต้นของ component property ตัวอย่างต่อไปนี้เป็นการกำหนดค่าเริ่มต้นให้
"prefix" property ของ StaffDetailComponent เป็นข้อความตายตัว ไม่ใช้นิพจน์ของ template
Angular จะทำแค่กำหนดค่าให้กับมันและไม่ทำอะไรนอกจากนั้น
<staff-detail prefix="My string" [staff]="currentStaff"></staff-detail>
"staff" property ยังคงทำการเชื่อมโยงข้อมูลกับ "currentStaff" property อยู่
จะเลือกการเชื่อมโยงแบบ Property หรือแบบแทรกค่าตัวแปร (Interpolation) ดี
บ่อยครั้งที่เรามีทางเลือกในการที่จะเลือกว่าจะใช้การแทรกค่าตัวแปร หรือใช้การเชื่อมโยงด้วย property
คู่ของการเชื่อมโยงต่อไปนี้ ทำงานเหมือนกัน
<p><img src="{{staffImageUrl}}"> is the <i>interpolated</i> image.</p> <p><img [src]="staffImageUrl"> is the <i>property bound</i> image.</p> <p><span>"{{title}}" is the <i>interpolated</i> title.</span></p> <p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
ทบทวนเกี่ยวกับการแทรกค่าตัวแปร (Interpolation) ได้ที่บทความ
รู้จักกับ Template Syntax ใน Angular เบื้องต้น
https://www.ninenik.com/content.php?arti_id=786 via @ninenik
ในหลายๆ กรณี การแทรกค่าตัวแปร เป็นวิธีการที่ค่อนข้างสะดวกสำหรับเป็นทางเลือก
การเชื่อมโยงข้อมูลด้วย property
หากมีการแสดงค่าข้อมูลในรูปแบบ string เราสามารถเลือกใช้รูปแบบใดๆ ก็ได้ ขึ้นกับความสะดวก
หรือความชื่นชอบของแต่ละคน ไม่มีเหตุผลทางเทคนิคใดๆ ที่จะบอกได้ว่าวิธีการไหนเหมาะสมหรือดี พอที่จะ
แนะนำให้เลือกใช้วิธีใดวิธีหนึ่งพิเศษกว่าได้
แต่เมื่อใดก็ตามที่เรามีการกำหนดค่าของ property ของ element ที่ไม่ใช่ค่า string เราต้องใช้รูปแบบ
การเชื่อมโยงแบบ property binding
ความปลอดภัยของเนื้อหา
ลองจินตนาการถึงเนื้อหาที่ไม่พึงประสงค์ต่อไปนี้
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
ใน Angular การเชือมโยงข้อมูลที่มีรูปแบบ HTML ที่มีอันตรายหรือมีความเสี่ยงจาก script นั้น Angular จะทำความ
สะอาดค่านั้นๆ ก่อนนำค่ามาแสดง คือจะไม่อนุญาตให้ <script> ใน HTML tag รั่วไหลออกมาทางบราวเซอร์ ไม่ว่า
จะเป็นกรณีการใช้การแทรกค่าตัวแปร หรือการเชื่อมโยงด้วย property ก็ตาม
สมมติเราเรียกใช้ดังตัวอย่างต่อไปนี้
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` <p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p> <p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p> `, }) export class AppComponent { evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax'; }
ในหน้าบราวเซอร์แสดงผล Angular จะสร้างข้อความเตือนในลักษณะแสดงค่าของ
script ออกมาในรูปแบบ string ข้อความธรรมดา ดังนี้
"Template <script>alert("evil never sleeps")</script>Syntax" is the interpolated evil title.
"Template alert("evil never sleeps")Syntax" is the property bound evil title.
การเชื่อมโยงด้วย attribute - Attribute binding
เราสามารถกำหนดค่าของ attribute ได้โดยตรงด้วยการใช้วิธีการเชื่อมโยงข้อมูลด้วย attribute
และนี่คือข้อยกเว้นเดียวสำหรับกฎที่ว่าการเชื่อมโยงข้อมูลสำหรับกำหนดค่า property เป้าหมายเท่านั้น
โดยการเชื่อมโยงข้อมูลด้วย attribute จะเป็นวิธีเดียวที่สามารถสร้างและกำหนดค่า attribute ได้
จากแนวทางที่ผ่านๆ มา เราเน้นย้ำเสมอว่า ควรใช้การเชื่อมโยงด้วย property ในการกำหนดค่าให้กับ
property ของ element จะดีกว่าการกำหนดค่า attribute ด้วยค่า string แล้วทำไม Angular ยัง
เสนอรูปแบบการเชื่อมโยงข้อมูลด้วย attribute อีก?
เหตุผลก็คือ "เรายังต้องใช้การเชื่อมโยงด้วย attribute เมื่อ element นั้นๆ ไม่มี property สำหรับ
ทำการเชื่อมโยง"
ยกตัวอย่างการขยายความกว้างใน table เช่น "colspan" ของ <td> ใน table element นั้นไม่มี
property ที่สอดคล้องกับค่า "colspan" หรือไม่มี property ที่ชื่อ "colspan" จึงไม่มี property เป้าหมาย
ที่จะทำการเชื่อมโยงได้
อย่างสมมติเราเขียนในลักษณะนี้
<tr> <td colspan="{{1 + 1}}">One-Two</td> <td>Three</td> <td>Four</td> </tr>
Angular จะแจ้ง error ทาง console ว่า " Can't bind to 'colspan' since it isn't a known property of 'td' "
ซึ่งหมายถึง <td> element นั้นไม่มี "colspan" property มีแต่ "colspan" attribute และการแทรกค่าตัวแปร
หรือนิพจน์ {{1+1}} นั้นเป็นการเชื่อมโยงข้อมูลด้วย property เท่านั้นไม่ใช่การเชื่อมโยงกับ attribute
ดังนั้นจึงเป็นเหตุผลที่เราจำเป็นต้องใช้การเชื่อมโยงด้วย attribute ในการสร้าง attribute แล้วเชื่อมโยง
รูปแบบการกำหนดการเชื่อมโยงด้วย attribute จะคล้ายๆ กับการเชื่อมโยงด้วย property เป็นดังนี้
[attr.colspan]="expression"
ในปีกกาสี่เหลี่ยม ให้ขึ้นต้นด้วยคำว่า "attr" ต่อด้วย (.) แล้วต่อด้วยชื่อของ attribute ที่ต้องการกำหนด
ซึ่งจากตัวอย่างการกำหนด colspan ใน <td> ข้างต้นเราก็จะได้เป็น
<tr> <td [attr.colspan]="1 + 1">One-Two</td> <td>Three</td> <td>Four</td> </tr>
หลักๆ แล้ว การเชื่อมโยงข้อมูลด้วย attribute จะใช้สำหรับในกรณีการกำหนดค่า "ARIA" attribute
เป็นการกำหนดข้อความที่ใช้ในเทคโนโลยีการให้การช่วยเหลือสำหรับผู้มีปัญหาด้านสายตา เช่น เป็นข้อความ
ที่ตัวช่วยเหลืออ่านออกมาเป็นเสียงแทน (screen reader) ตัวอย่าง
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
การเชื่อมโยงด้วย class - Class binding
เราสามารถเพิ่มหรือลบชื่อของ css class ใน "class" attribute ของ element ได้ด้วยใช้วิธีการเชื่อมโยงด้วย class
รูปแบบการกำหนดการเชื่อมโยงด้วย class จะคล้ายๆ กับการเชื่อมโยงด้วย property เป็นดังนี้
[class]="expression"
ตัวอย่างเช่น ปกติเรากำหนด class attribute เป็นแบบนี้
<button class="btn btn-primary" > Save </button>
ปุ่มนี้มีชื่อ css class สองชื่อคือ btn และ btn-primary เป็น css class ของ bootstrap ที่เราพอคุ้นเคยมาบ้างแล้ว
ทีนี้เราใช้การเชื่อมโยงด้วย class กำหนดเป็น
<button class="btn btn-primary" [class]="myclass"> Save </button>
โดย myclass เป็นค่าตัวแปร component property สมมติให้เท่ากับ myclass = "btn btn-danger";
การกำหนดการเชื่อมโยงด้วย class ข้างต้น จะเป็นการรีเซ็ตค่าของ class attribute เป็นค่าว่างแล้ว
เอาค่าใหม่ไปใส่แทน ผลลัพธ์ที่ได้จะกลายเป็น
<button class="btn btn-danger"> Save </button>
นอกจากรูปแบบการกำหนดข้างต้นแล้ว เรายังสามารถกำหนดในรูปแบบที่คล้ายๆ กับการกำหนดใน attribute
มีรูปแบบเป็นดังนี้
[class.class-name]="expression"
class-name ก็คือชื่อของ css class ที่เราต้องการใช้งาน
<button class="btn" [class.btn-danger]="isDanger"> Save </button>
จากตัวอย่างการใช้งานในรูปแบบ [class.class-name] ตามด้านบน จะต่างจากรูปแบบแรก ตรงนี้รูปแบบที่สองนั้น
เป็นการ เพิ่มเข้า หรือนำออกของ css class ที่เรากำหนด โดย css class เดิมที่มีอยู่แล้วจะไม่ถูกรีเซ็ต หรือคล้าย
กับการใช้หรือไม่ใช้ css class ที่ต้องการ
[class.btn-danger]="isDanger"
ลักษณะนี้คือ css class ที่ชื่อ btn-danger จะถูกเพิ่มเข้าไปหรือนำออกมาจาก ปุ่มที่เดิมมี css class ที่ชื่อ btn อยู่แล้ว
โดยถ้าค่า isDanger ที่เป็นตัวแปรใน property component มีค่าเป็น true ก็หมายถึงให้เพิ่มค่าเข้าไป ถ้าเป็น false
ก็ให้นำค่าออกมาถ้ามีค่านั้นอยู่ก่อน ตัวอย่างกรณีเป็น true ผลที่ได้ก็จะเป็น
<button class="btn btn-danger"> Save </button>
ซึ่งลักษณะการนำเข้าหรือนำออก css class ด้วยรูปแบบ [class.class-name]="expression" ข้างต้น จะสามารถ
ทำได้แค่เพียง css class ชื่อเดียวในแต่ละครั้งเท่านั้น ไม่สามารถใช้พร้อมกันหลายๆ ชื่อ แต่จุดนี้เราจะมี ngClass
directive มาเป็นตัวเลือกใช้แทนกรณีต้องการกำหนดหลายๆ css class พร้อมกัน เราจะได้ดูในรายละเอียดต่อๆ ไป
การเชื่อมโยงด้วย style - Style binding
เราสามารถกำหนด style ให้กับ element ในรูปแบบ inline หรือใน tag element นั้นๆ ได้ด้วยการเชื่องโยง
ด้วย style
รูปแบบการกำหนดการเชื่อมโยงด้วย style จะคล้ายๆ กับการเชื่อมโยงด้วย property โดยในปีกกาสี่เหลี่ยมจะ
ขึ้นต้นด้วยคำว่า "style" ต่อด้วย (.) และต่อด้วยชื่อของ css style property ที่ต้องการ
[style.style-property]="expression"
ตัวอย่าง
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
ถ้าตัวแปร isSpecial ใน component property เป็น true ปุ่มนี้จะมีสีข้อความเป็นสีแดง ถ้าเป็น false จะเป็นสีเขียว
สมมติเป็น true ผลที่ได้จะเป็น
<button style="color: red;">Red</button>
สังเกตว่าเป็นการกำหนด css style แบบ inline ให้กับ element
ชื่อของ style-property เราสามารถกำหนดได้ทั้งในรูปแบบ dash-case ตามตัวอย่างด้านบน หรือกำหนดแบบ
camelCase แบบนีก็ได้
<!-- การกำหนด style-property รูปแบบ dash-case --> <button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button> <!-- การกำหนด style-property รูปแบบ camelCase --> <button [style.backgroundColor]="canSave ? 'cyan': 'grey'" >Save</button>
ใน style-property บางอันจะมีหน่วยของค่าที่เรากำหนดด้วย ตัวอย่างเช่น font-size อาจจะมีหน่วยเป็น
em หรือ px หรือ % หรืออื่นๆ เราก็สามารถต่อส่วนของชื่อหน่วยที่ต้องการลงไปดังนี้ได้
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button> <button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
การใช้งานการเชื่อมโยงด้วย style ข้างต้นเราสามารถเชื่องโยงกับ style-property ได้ทีละหนึ่ง property เท่านั้น
ไม่สามารถใช้พร้อมกันหลายๆ อันได้ ซึ่งเราจะมี ngStyle directive มาช่วยให้เราสามารถที่จะกำหนด style-property
พร้อมๆ กันหลายอันได้ รายละเอียดจะนำเสนอในบทความต่อๆ ไป
การเชื่อมโยงด้วย event - Event binding ( (event) )
การเชื่อมโยงที่ผ่านๆมา ทิศทางการไหลของข้อมูลจะเป็นไปในทิศทางเดียว One-way โดยจาก
ค่าของ component property มายัง element ใน template
อย่างที่เราทราบกันดีว่า user ไม่ได้เพียงแค่จ้องดูอยู่ที่หน้าจอเท่านั้น ยังมีกิจกรรมอื่นๆ เกิดขึ้นตามมาตลอด เช่น
มีการกรอกข้อมูลในช่อง input มีการเลือกรายการจากลิสต์รายการ มีการคลิกที่ปุ่ม เหล่านี้เป็นต้น
โดยการกระทำต่างๆ ที่เกิดขึ้นมีผลต่อการไหลหรือการเคลื่อนที่ของข้อมูลในทิศทางตรงกันข้าม คือ จาก
element ใน template ไปยังค่าตัวแปรของ component property
วิธีการเดียวที่เราจะรู้ได้ว่า user มีการกระทำใดๆ เกิดขึ้นก็คือการตรวจจับ event ต่างๆ เช่น การเคาะแป้น การเลื่อน
เมาส์ไปมา การคลิก การแตะ เหล่านี้ โดยเรากำหนดค่า event จากการการกระทำของ user ที่เราสนใจ ด้วยรูปแบบ
การเชื่อมโยงด้วย event binding ใน Angular
รูปแบบการเชื่อมโยงด้วย event นั้น เราจะกำหนด event เป้าหมาย หรือที่เรียกว่า "target event" ไว้ภายในวงเล็บ
และด้านขวามือของเครื่องหมาย = ก็จะเป็น template statement อยู่ในเครื่องหมายคำพูด หรือที่เราเรียกว่า
คำสั่งควบคุม template ที่เราได้ อธิบายไปแล้วในบทความที่ผ่านมา
"ทบทวนบนความได้ที่"
รู้จักกับ Template Syntax ใน Angular เบื้องต้น
https://www.ninenik.com/content.php?arti_id=786 via @ninenik
เราจะได้รูปแบบของการเชื่อมโยงด้วย event เป็นดังนี้
(event)="template statement"
ยกตัวอย่างเช่น
<button (click)="onSave()">Save</button>
event เป้าหมาย - Target event
ชื่อที่เรากำหนดในวงเล็บ () อย่างเช่น (click) เป็นการระบุถึง event เป้าหมาย ตัวอย่างต่อไปนี้ เป็นการระบุว่า
click เป็น event เป้าหมาย
<button (click)="onSave()">Save</button>
หรือเราสามารถกำหนดในรูปแบบที่ไม่มีวงเล็บ แทนได้ โดยให้นำหน้าด้วยคำว่า "on-" แล้วตาม
ด้วยขื่อ event เป้าหมายที่ต้องการ เช่น
<button on-click="onSave()">Save</button>
นอกจาก event element ต่างๆ ที่เราสามารถกำหนดเป็น event เป้าหมายแล้ว Angular ยังให้เราสามารถใช้งาน
directive ที่มี event property ได้อีกด้วย ตัวอย่างเช่น
<!-- `myClick`คือ event ที่กำหนดขึ้นมาเองใน directive ที่ชื่อ `ClickDirective` --> <div (myClick)="clickMessage=$event" clickable>click with myClick</div>
เกี่ยวกับ event ของ directive เราจะได้อธิบายเพิ่มเติมต่อไป
ในกรณีที่เรากำหนด event เป้าหมายผิด หรือ ไม่มี event เป้าหมายนี้ Angular จะแจ้งเตือนผ่าน console ว่า
"unknown directive"
$event และการจัดการคำสั่งของ event
Angular จะกำหนดตัวดำเนินการเกี่ยวกับ event เป้าหมาย ในการเชื่อมโยงด้วย event
โดยเมื่อมี event เกิดขึ้น ตัวดำเนินการจะประมวลผลคำสั่งควบคุม template หรือ template statement
โดยคำสั่งควบคุม template นั้น โดยทั่วไปแล้ว จะเกี่ยวพันกับตัวรับค่า ที่แสดงผลตอบรับกับ event นั้นๆ
อย่างเช่น การเก็บค่าจาก HTML control เช่นพวกฟอร์ม input ต่างๆ มาไว้ใน model หรือตัวแปรใน component
ตัวกลางที่เป็นสื่อที่ให้ข้อมูลเกี่ยวกับ event โดยรวมถึงค่าของข้อมูล เราเรียกชื่อ event object นั้นว่า $event
จริงๆ เราได้พอรู้จักมาแล้วบ้างในบทความ
"ทบทวนบทความเกี่ยวกับ $event ได้ที่"
การรับค่า input จาก user ใน Angular App เบื้องต้น
https://www.ninenik.com/content.php?arti_id=769 via @ninenik
โดยหน้าตาของ event object นั้นก็จะขึ้นอยู่กับ event เป้าหมาย ซึ่งถ้า event เป้าหมายเป็น DOM element event
ดั้งเดิม เช่น click keyup หรืออื่นๆ แล้ว ตัว $event ก็จะเป็น DOM event object ที่มี property อย่าง target และ
target.value
ดูตัวอย่างประกอบ
<input [value]="currentStaff.name" (input)="currentStaff.name=$event.target.value" >
โค้ดตัวอย่างข้างต้นมีการกำหนดค่า "value" property ให้กับ input เชื่อมโยงกับค่า currentStaff.name หรือ
"name" property ของ object ที่ชื่อ currentStaff จากนั้นมีการตรวจจับการเปลี่ยนแปลงของค่า "value" property
โดยการเชื่อมโยงด้วย "input" event โดยเมื่อ user ทำการเปลี่ยนแปลงค่า และเกิด input event ขึ้น การเชื่อมโยง
ก็จะไปประมวลผลคำสั่งการทำงาน "currentStaff.name=$event.target.value" ซึ่งในคำสั่งนี้มี DOM event object
ที่อ้างอิงด้วยตัวแปร $event อยู่
ในการเปลี่ยนแปลงค่า "name" property ของ currentStaff object ให้มีค่าเท่ากับ $event.target.value
$event.target ก็คือ input element ดังนั้นคำสั่งนี้ก็คือ ให้ currentStaff.name มีค่าเท่ากับค่าของ input element
ที่มีการเปลี่ยนแปลง
ถ้า event นั้นๆ เป็นของ directive (จำไว้ว่า component ก็คือ directive) แล้ว $event จะมีหน้าตาตามที่ directive
นั้นๆ กำหนดขึ้นมาใช้งาน
กำหนด event ขึ้นมาเองด้วย EventEmitter
โดยทั่วไป directive จะเกิด event ที่กำหนดเองขึ้นจากการใช้งาน Angular EventEmitter โดย directive จะสร้าง
EventEmitter และนำออกมาใช้เป็น property ของ directive จากนั้น Directive จะเรียกใช้คำสั่ง
EventEmitter.emit(payload) ในการปล่อย event ออกมา แล้วส่งผ่านเข้าไปแทนในตำแหน่งข้อความคำว่า payload
โดยเป็นค่าอะไรก็ได้ ต่อจากนั้น Directive หลักก็จะตรวจจับ event โดยการเชื่อมโยงกับค่า property นี้
และสามารถเข้าถึง payload ผ่านทาง $event object โดยเนื้อหาเกี่ยวกับการกำหนด event ขึ้นมาใช้งาน
เราจะได้อธิบายเพิ่มเติมในบทความต่อๆ ไป
การเชื่อมโยงแบบสองทิศทาง - Two-way binding ( [(...)] )
บ่อยครั้งเราต้องการที่จะทำการแสดงข้อมูลพร้อมกับอัพเดทข้อมูลไปด้วย เมื่อ user มีการเปลี่ยนแปลงค่า
วิธีการเชื่อมโยงแบบ Two-way จึงเป็นวิธีที่ถูกนำมาใช้งานในกรณีดังกล่าวนี้ ซึ่งเราเคยได้ผ่านตามาบ้างแล้ว
ในบทความเกี่ยวกับการใช้งาน form และ ngModel
"ทบทวนบทความได้ที่"
การใช้งาน Form กับ การเชื่อมข้อมูลแบบสองทาง ใน Angular เบื้องต้น
https://www.ninenik.com/content.php?arti_id=775 via @ninenik
Angular ใช้รูปแบบการเชื่อมโยงแบบสองทิศทางโดยใช้ [(x)] ซึ่งถ้าเราแยกพิจารณา ก็จะเหมือนกับ
กำหนดปีกกาสี่เหลี่ยม [] ให้กับ (x) การเชื่อมโยงด้วย event และการกำหนด้วยปีกกาสี่เหลี่ยมก็คือ
การเชื่อมโยงด้วย property ดังนั้น [(x)] จึงหมายถึงการเชื่อมโยงด้วย property ของการเชื่อมโยง
ด้วย event
ดูตัวอย่างโค้ดนี้
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` <input [value]="myvalue" (input)="myvalue=$event.target.value" /> <br> {{myvalue}} `, }) export class AppComponent { myvalue = 10; }
จากโค้ดตัวอย่างด้านบนเราจะเห็นว่า input element มีการเชื่อมโยงด้วย property โดยกำหนด
[value]="myvalue"
ให้ "value" property มีค่าเท่ากับ myvalue ซึ่งเป็นค่าของ component property
ทิศทางการไหลของข้อมูลก็คือจาก component มายัง element
และในขณะเดียวกันก็มีการกำหนด
(input)="myvalue=$event.target.value"
ให้ค่า "myvalue" ใน component property มีค่าเท่ากับค่าของ input element ที่เปลี่ยนแปลง
เมื่อมี "input" event เกิดขึ้น ทิศทางการไหลของข้อมูลก็คือจาก element ไปยัง component
การเคลื่อนไหวของข้อมูลหรือการไหลของข้อมูลในลักษณะข้างต้น ก็คือ Two-way data binding
หรือก็คือการเชื่อมโยงแบบสองทิศทาง
ดังนั้นถ้าอ้างอิงจากรูปแบบการกำหนดโดยใช้ [(x)]
(x) ก็คือ property ที่มีค่าเท่ากับ (input)="myvalue=$event.target.value"
[(x)] จึงหมายถึง การเชื่อมโยงด้วย property ของการเชื่อมโยง event
ทำให้ค่าของ property แปรผันไปตามค่าของ การเชื่อมโยง event และในขณะเดียวกัน
การเชื่อมโยงของ event ก็ทำให้ค่าของ component propert เปลี่ยนแปลงไปด้วย
จึงเกิดการเชื่อมโยงแบบสองทิศทางขึ้นเมื่อใช้รูปแบบ [(x)]
อย่างที่เราได้เรียนรูปมาบ้างแล้ว Angular มี ngModel มาช่วยจัดการเกี่ยวกับ
การเชื่อมโยงข้อมูลแบบสองทิศทาง โดยเฉพาะการนำไปใช้งานกับ form element ต่างๆ
นั้นทำได้สะดวกขึ้น
ตอนนี้เราได้เข้าใจ และรู้จักการเชื่อมโยงข้อมูลในรูปแบบต่างๆ เพิ่มขึ้นพร้อมกับได้ทบทวนรูปแบบการเชื่อมโยง
ที่เราเคยได้ใช้ไปแล้วในบทความก่อนๆ หน้าไปด้วย เนื้อหาส่วนนี้ยังอยู่ในส่วนของการกำหนด template หรือยัง
เกี่ยวข้างกับการใช้งาน template ต่อเนื่องจากตอนที่แล้วอยู่
รู้จักกับ Template Syntax ใน Angular เบื้องต้น
https://www.ninenik.com/content.php?arti_id=786 via @ninenik
ในตอนหน้าเรายังอยู่กับการจัดการในส่วนของการใช้งาน template อยู่ เราจะไปดูเกี่ยวกับการใช้งาน directive
ที่ยังค้างกันไว้ เช่น ngClass ngStyle รวมถึง directive รูปแบบอื่นๆ เพิ่มเติม รอติดตาม