[Angular2#3] สร้าง Module และ Component ด้วย Angular2

Nuttavut Thongjor

บทความก่อนหน้านี้เราแนะนำให้รู้จักกับ angular-cli เครื่องมือสร้างโปรเจคและไฟล์ของ Angular 2 กันไปแล้ว บทความนี้จะมาเริ่มใช้งาน Angular 2 แบบจริงๆจังๆกันซักทีครับ

เพื่อไม่ให้เพื่อนๆหลงประเด็น เราขอแนะนำอย่างยิ่งให้เพื่อนๆอ่าน รู้จัก Angular 2 โครงสร้างและคอนเซ็ปต์ของแอพพลิเคชันใน Angular 2 กันก่อนครับ สำหรับเพื่อนๆคนไหนที่ยังไม่เคยเซย์ฮัลโหลวววกับ ES2015 มาก่อนเลย ผู้เขียนก็แนะนำให้อ่านบทความ พื้นฐาน ES2015 (ES6) สำหรับการเขียน JavaScript สมัยใหม่ ก่อนเช่นกันครับ

หมายเหตุตัวโตๆ ตรวจสอบเวอร์ชันของ angular-cli ให้แน่ชัดก่อนครับ บทความนี้เราจะใช้ angular-cli เวอร์ชันมากกว่าหรือเท่ากับ angular-cli@1.0.0-beta.11-webpack.2 ที่สนับสนุนการทำงานกับ Angular2 RC5 ผ่านคำสั่ง npm install -g angular-cli@webpack

เมื่อ Angular 2 เข้าสู่ยุคของคอมโพแนนท์

โลกเปลี่ยน เฟรมเวิร์กเปลี่ยน แนวคิดจึงเปลี่ยนตาม Angular2 เป็นอีกหนึ่งเฟรมเวิร์กที่ลอกคราบออกมาจากเปลือก ก็สนับสนุนความเป็นคอมโพแนนท์แบบเต็มตัว เพื่อให้อธิบาย Angular2 ได้ลื่นปรื๊ดดั่งเหยียบเปลือกกล้วย ผมจะแนะนำสมาชิกใหม่ของเราก่อน... น้องคอมโพแนนท์

ท่ามกลางขุนเขาและป่าไม้ กระท่อมของคุณตั้งอยู่บนลานกว้าง ส่องตาซ้ายเห็นทุ่งนา ส่องตาขวาเห็นกวางเรนเดียร์ แต่ยกมือถือขึ้นมาส่องทีไรช่างไร้วี่แววโปเกมอนทุกที่... เจ็บปวด

บ้านของคุณอยู่กลางเขา ถ้าบ้านคุณไม่มีรั้วบ้านมิดชิด สรรพสัตว์น้อยใหญ่ (แน่หละ ไม่รวมโปเกมอนนะ เพราะมันไม่มี!) ก็อาจบุกเข้าบ้านคุณได้ เหนือสิ่งอื่นใดการออกแบบบ้านที่ดีคุณต้องคำนึงถึงประโยชน์ใช้สอย ไม่ควรรวมหลายสิ่งอย่างมาไว้ในบ้านตน ถ้าคุณคือผู้ใหญ่บ้านแล้วดันตั้งศาลประจำหมู่บ้านไว้ภายในบ้านตัวเอง ชีวิตคุณก็จะมีแต่ความวุ่นวาย จะนอนซะหน่อยตีสองก็ยังมีคนมาร้องทุกข์ นั่นหละครับเพราะบ้านของคุณมีสองหน้าที่ในเวลาเดียวกัน

คอมโพแนนท์คือส่วนหนึ่งของหน้า UI เราครับ เป็นส่วนประกอบเล็กๆที่เหมือนกับบ้านของเรา เราต้องแน่ใจว่าส่วนประกอบชิ้นนี้ของเรามีรั้วรอบขอบชิด ไม่ใช่ใครก็เข้าไปจัดการกับคอมโพแนนท์เราได้ มีเพียงประตูหรือทางเข้าออกเท่านั้นที่เราจะให้คอมโพแนนท์ของเราติดต่อกับโลกภายนอกได้

คอมโพแนนท์หนึ่งตัวย่อมมีหนึ่งหน้าที่หลัก ถ้าคอมโพแนนท์ของเรามีหน้าที่แสดงเนื้อหาของบทความ เราต้องให้มันมีหน้าที่แสดงผลเท่านั้น อย่าให้มันทำอย่างอื่นเช่นไปดึงข้อมูลบทความจาก API Server ด้วย หากทำหลายอย่างในคอมโพแนนท์เดียวก็จะไม่ต่างอะไรกับผู้ใหญ่บ้านที่ดันยกศาลมาไว้ในบ้านของตน

separate-component

จากรูปข้างต้น เราสามารถแบ่งส่วนการแสดงผลได้เป็นสองส่วนหลักๆ ส่วนบนสุดมีหน้าที่แสดงผลส่วนหัวของเพจ เราจึงแยกมาสร้างเป็น Header Component ส่วนล่างที่แสดงข้อมูลของวิกิทั้งหมดเป็นอีกหน้าที่ คือแสดงผลวิกิเพจทั้งหมด เราจึงแยกออกมาเป็นอีกหนึ่งคอมโพแนนท์เช่นกัน

หลังจากเพื่อนๆเข้าใจคอมโพแนนท์เรียบร้อยแล้ว เรากลับบ้านพักผ่อนกัน กลางดึกคืนนั้นหัวหน้าเผ่าจากหมู่บ้านข้างๆนำลูกบ้านบุกขโมยเสบียงในหมู่บ้านของคุณ เช้าวันรุ่งขึ้นทุกคนในหมู่บ้านจึงปรึกษากัน ครั้นจะให้ผู้ใหญ่บ้านของเราออกหมายเรียกผู้นำหมู่บ้านที่ก่อเหตุเพื่อจับกุมมาเฆี่ยนซักพันทีก็ไม่ได้ เพราะอำนาจและขอบเขตของผู้ใหญ่บ้านเราก็อยู่แค่ในหมู่บ้านเราเท่านั้น เวลาผ่านไปร่วม 2 นาที ทุกคนเห็นพร้องกันว่าเราจะไปร้องเรียนกับเจ้าเมือง ทุกหมู่บ้านภายใต้เมืองนี้ต้องอยู่ภายใต้กฎเกณฑ์และเงื่อนไขที่เรียกว่ากฎหมายเดียวกัน

คอมโพแนนท์ของเราก็มีลักษณะเดียวกันครับ ในแอพพลิเคชันขนาดใหญ่เราอาจต้องการรวมกลุ่มคอมโพแนนท์ที่เกี่ยวข้องกันเข้าด้วยกัน โดยคอมโพแนนท์เหล่านั้นจะต้องสื่อสารด้วยกฎเดียวกัน เช่น มี dependency เหมือนกันเป็นต้น เมื่อเป็นเช่นนี้เราจึงควรรวมกลุ่มคอมโพแนนท์ที่เกี่ยวข้องกันเหล่านั้นเข้าด้วยกันครับ ถ้าเรามีกลุ่มของคอมโพแนนท์ที่จัดการเกี่ยวกับฟอร์ม เช่น Input Component, TextArea Component, RadioButton Component ลักษณะแบบนี้เราสามารถรวมกลุ่มกันให้อยู่ภายใต้สิ่งที่เรียกว่าโมดูลเดียวกันได้ครับ เราเรียกโมดูลที่เกิดจากการรวมกลุ่มของสิ่งที่สัมพันธ์กันว่า feature modules

แอพพลิเคชันหนึ่งๆเกิดจากการผสานกันของความสามารถต่างๆ เพื่อผลักดันให้แอพพลิเคชันนั้นเป็นรูปเป็นร่างขึ้นมา เมืองหนึ่งเมืองก่อความเป็นชาติขึ้นไม่ได้ ต้องรวมกลุ่มกันหลายเมืองจึงจะเป็นประเทศ แอพพลิเคชันของเราก็เช่นกันจะสามารถทำงานได้เมื่อมีโมดูลตั้งแต่หนึ่งโมดูลที่มีความสามารถแตกต่างกันมารวมกัน เช่น แอพพลิเคชันหนึ่งอาจต้องการ RouterModule สำหรับจัดการเส้นทาง และต้องการ FormsModule สำหรับการสร้างฟอร์ม เป็นต้น

สร้างโปรเจคกับ angular-cli

ถึงเวลาสร้างโปรเจคกันแล้วครับ หลังจากเพื่อนๆได้ติดตั้ง angular-cli จากบทความที่แล้วกันแล้ว ขอให้สร้างโปรเจคด้วยคำสั่งนี้

Code
1ng new series-angular2-wiki --style=scss

--style=scss เป็นการบอกว่าเราจะใช้ SCSS ในโปรเจคนี้ครับ

เนื่องจากเราใช้เครื่องมือคือ angular-cli ในการสร้างโปรเจคแล้ว จึงขอให้เพื่อนๆเรียนรู้เกี่ยวกับเฟรมเวิร์กว่ามันทำอะไรให้น้อยที่สุดก่อน แล้วมุ่งประเด็นไปที่คอนเซ็ปต์และการเขียนโค๊ดด้วย Angular2 เท่านั้นครับ

อธิบายโครงสร้างไฟล์อย่างรวดเร็ว

ตลอดทั้งบทความเราจะยุ่งอยู่กับโฟลเดอร์ src อันเป็นที่บรรจุส่วนของโค๊ดที่เราจะจัดการครับ โฟลเดอร์อื่นนอกเหนือจากนี้ให้เราแค่ชายตามองก็พอ เราคงได้กลับมาคุยเรื่องของมันกันหลังเซเว่นปิด (แบบนี้ก็ได้หรออ)

Code
1src
2|--- app
3 |--- environments
4 |--- shared
5 |--- app.component.html
6 |--- app.component.scss
7 |--- app.component.ts
8 |--- app.component.spec.ts
9 |--- app.module.ts
10 |--- index.ts
11|--- index.html
12|--- main.ts

ความจริงแล้วยังมีไฟล์อื่นๆใน src อีกครับ แต่เราจะหยิบมาพูดเฉพาะไฟล์สำคัญเท่านั้นครับ เริ่มจาก app.module.ts กันก่อนเลย

TypeScript
1import { BrowserModule } from '@angular/platform-browser'
2import { NgModule, ApplicationRef } from '@angular/core'
3import { CommonModule } from '@angular/common'
4import { FormsModule } from '@angular/forms'
5import { AppComponent } from './app.component'
6
7@NgModule({
8 declarations: [AppComponent],
9 imports: [BrowserModule, CommonModule, FormsModule],
10 providers: [],
11 entryComponents: [AppComponent],
12 bootstrap: [AppComponent],
13})
14export class AppModule {}

ตามที่เราได้กล่าวไปแล้วครับ โมดูลเป็นที่รวมของคอมโพแนนท์ service directive และอื่นๆที่มีความสัมพันธ์กันรวมเข้าไว้ด้วยกัน ตอนนี้เรากำลังจะสร้างแอพพลิเคชันของเราครับ แน่นอนว่าแอพพลิเคชันเราก็เหมือนบรรจุภัณฑ์ที่รวม business logic ที่สัมพันธ์กันไว้ด้วยกัน จึงถือเป็นโมดูลตัวหนึ่ง

วิธีการสร้างโมดูลนั้นแสนง่ายเพียงเราแปะ @NgModule เข้าไปพร้อมใส่ metadata อีกนิดหน่อย เมื่อส่วนผสมลงตัวเราก็จะได้โมดูลที่แสนหอมหวานออกมาครับ

AppModule เป็นโมดูลเพราะมีการแปะ @NgModule เอาไว้ ข้อกำหนดของ Angular2 กล่าวไว้ว่าในหนึ่งแอพพลิเคชันต้องมีโมดูลอย่างน้อยตัวนึง โมดูลตัวนี้หละครับเราเรียกว่า root module และแน่นอนมันจะเป็นตัวอื่นไม่ได้เพราะมันคือ AppModule ของเรานั่นเอง

โมดูลเดียวนั้นช่างแห้งเหี่ยว เหมือนความเปล่าเปลี่ยวที่ไร้รัก โลกความเป็นจริงแอพพลิเคชันของเราประกอบจากโมดูลหลายตัวครับ เช่น เราต้องการแสดงผลเราจึงใช้ BrowserModule ทั้งยังต้องการจัดการกับฟอร์มของ HTML เราจึงมี FormsModule

เพื่อให้การประกอบร่างจากโมดูลอื่นสำเร็จ เราจึงมี metadata ชื่อ imports เพื่อให้เรายัดโมดูลที่จะนำมาใช้ในแอพพลิเคชันของเรา แน่นอนครับว่าเมื่อเป็นโมดูลที่เอามาใช้กับแอพพลิเคชันของเรา ทุกส่วนภายใต้แอพเราก็จะรับรู้ถึงการมีอยู่ของโมดูลเหล่านั้นทั้งหมด

แอพพลิเคชันเราที่ถือเป็น AppModule นั้น เราสามารถกำหนดได้ว่าจะให้มีคอมโพแนนท์อะไรอยู่บ้าง หากเราระบุชื่อของคอมโพแนนท์เหล่านั้นลงใน declarations ส่วนอื่นในโมดูลเราก็จะรับรู้ว่ามีคอมโพแนนท์นี้อยู่

app.module.ts ของเรามีการระบุ AppComponent ใน declarations เป็นผลให้คอมโพแนนท์อื่นๆในโมดูลของเรารับรู้ได้ว่านี่คือคอมโพแนนท์ของโมดูล สุดท้ายแล้วไม่ว่าใครก็เข้าถึงได้เพราะเราอยู่ร่วมโมดูลเดียวกันและเธอกับฉันก็เป็นของกันและกันในที่สุด

ถึงตอนนี้หลายคนคงสงสัยครับว่า Angular2 รู้ได้อย่างไรว่าจะหยิบคอมโพแนนท์ไหนไปเป็นตัวตั้งต้นในการทำงาน จะเลือกใครไปแปลงเป็นก้อนข้อมูลฝังใน DOM ดี?

Angular2 นั้นจะหยิบสิ่งที่เรียกว่า bootstrap component ไปปู้ยี้ปู้ยำเป็นตัวแรกครับ แน่นอนครับว่ามันคือสื่งที่ระบุใน metadata ที่ชื่อ bootstrap นั่นเอง

รู้จัก Bootstrap

กาลครั้งหนึ่งเมื่อ Angular 1 ยังเบ่งบาน เหล่านักพัฒนาเว็บผู้ใช้ Angular 1 ต่างรู้สึกตื้นตันใจที่ได้ใช้สุดยอดเฟรมเวิร์กแห่งยุค เวลาถัดมาสัญญาณชีพของ React เต้นดังขึ้นจนกระทั่งเรามี React Native ให้พลเมือง React เขียนแอพพลิเคชันแบบ native บนสมาร์ทโฟนได้ ชาวเมือง Angular1 อยากเขียนแอพพลิเคชันแบบ native ลงมือถือกับเขาบ้างแต่ก็ทำไม่ได้ สุดท้ายจาก Angular1 ก็เลยกลายเป็น Angry Bird โกรธจนควันออกหู แล้วก็ก้มหน้าก้มตาอยู่กับความช้าของ Ionic กันต่อไป

React นั้นแยกส่วนการแสดงผลออกจากแพลตฟอร์มครับ ความหมายคือส่วนการแสดงผลของ React ไม่ได้อยู่ในไลบรารี่หลักของมันเอง หากแต่แยกออกมาเป็นอีกไลบรารี่ ถ้าเราอยากแสดงผลบนเบราเซอร์เราก็มี render ให้ใช้งาน แต่ถ้าอยากใช้ React เพื่อทำงานบนเซิร์ฟเวอร์ (Server Rendering) เราก็มี renderToString จากอีกไลบรารี่คือ react-dom/server เห็นไหมครับเมื่อส่วนแสดงผลแยกออกมาจากตัวไลบรารี่เองแล้ว เราจึงสามารถใช้ React ให้ไปปรากฎตัวอยู่ที่ไหนก็ได้ เพียงแค่เปลี่ยนตัวแสดงผลแค่นั้นเอง

การกลับมาครั้งยิ่งใหญ่ของ Angular2 จึงต้องมาพร้อมความใหญ่ยิ่ง ตอนนี้ Angular2 แยกส่วนแสดงผลไม่ให้ผูกติดกับแพลตฟอร์มเว็บอย่างเดียวอีกแล้ว แต่ตอนนี้เรากำลังจะใช้ Angular2 ทำเว็บกันใช่ไหมครับ เราจึงต้องเรียกใช้อะไรบางอย่างเพื่อให้แอพพลิเคชันของเราแสดงผลได้บนเว็บ และนั่นหละครับคือ bootstrap

เนื่องจากโค๊ดของแอพพลิเคชันเราฝังไว้ใน AppModule เราจึงบอก Angular compiler ว่า ได้โปรดเถอะช่วยเอาโมดูลของฉันไปทำงานทีนะเค้าขอร้อง และนั่นหละครับคือความหมายในบรรทัดที่ 10 ของเรา

TypeScript
1// app/main.ts
2import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
3import { enableProdMode } from '@angular/core'
4import { AppModule, environment } from './app/'
5
6if (environment.production) {
7 enableProdMode()
8}
9
10platformBrowserDynamic().bootstrapModule(AppModule)

ถ้าสังเกตดีๆในบรรทัดแรกจะพบว่า bootstrapModule ของเรานำเข้ามาจาก @angular/platform-browser-dynamic ชื่อมันฟ้องชัดเลยใช่ไหมครับว่าใช้กับ browser ส่วน platformBrowserDynamic ก็ไม่ได้อยู่ใน @angular/core นั่นไงครับเราแยกส่วนแสดงผลของ Angular2 จากส่วนหลักเป็นที่เรียบร้อยแล้ว ตัว Angular เองจึงใช้กับอะไรก็ได้ไม่ได้ขึ้นอยู่กับแค่แพลตฟอร์ตเว็บบนเบราเซอร์

ขั้นตอนการปลุกปล้ำให้ bootstrap component ของเราทำงานนี้เราเรียกว่า bootstrapping มีด้วยกันสองแบบคือ Dynamic bootstraping (ผ่าน JIT Compiler) และ Static bootstrapping (ผ่าน AOT Compiler) เราจะได้กลับมาพูดถึงเรื่องนี้กันอย่างละเอียดในบทความถัดๆไปครับ

สร้างหน้าโฮมเพจของแอพพลิเคชัน

เราทราบกันแล้วว่าคอมโพแนนท์เริ่มต้นของแอพพลิเคชันเราคือ AppComponent ซึ่งอยู่ใน src/app/app.component.ts เราจะมาทำความเข้าใจคอมโพแนนท์ให้มากขึ้นด้วยการเปิดมันขึ้นมาดูครับ

TypeScript
1import { Component } from '@angular/core'
2
3@Component({
4 selector: 'app-root',
5 templateUrl: 'app.component.html',
6 styleUrls: ['app.component.css'],
7})
8export class AppComponent {
9 title = 'app works!'
10}

เราใช้ @Component แปะเหนือคลาส AppComponent เพื่อบอกว่าคลาสนี้มีคุณสมบัติเป็นคอมโพแนนท์ ไส้ในที่อยู่ในตัว @Component เรียกว่าเป็น metadata (ถ้าเพื่อนๆคนไหนลืมไปแล้วแนะนำให้กลับไปอ่าน รู้จัก Angular 2 โครงสร้างและคอนเซ็ปต์ของแอพพลิเคชันใน Angular 2 อีกรอบครับ) โดย metadata ที่น่าสนใจมีดังนี้

  • templateUrl: จุดนี้เป็นการบอก Angular ว่าไฟล์ที่ใช้แสดงผลคือไฟล์ไหน
  • styleUrls: เช่นเดียวกันกับ templateUrl ส่วนนี้ใช้บอกว่า stylesheet ของเราอยู่ที่ไหน

ความเป็นจริงแล้วเราสามารถยัดส่วน template และ stylesheet ไว้ในคอมโพแนนท์นี้ได้เลยแบบนี้ครับ

TypeScript
1import { Component } from '@angular/core'
2
3@Component({
4 selector: 'app-root',
5 template: ` <h1 class="header">สวัสดีชาวโลก</h1> `,
6 styles: [
7 `
8 .header {
9 font-size: 36px;
10 }
11 `,
12 ],
13})
14export class AppComponent {
15 title = 'app works!'
16}

แต่ Angular มีข้อแนะนำว่าถ้า template มีความยาวเกิน 3 บรรทัด พวกเอ็งจงย้าย template ไปไว้ในไฟล์ซะ!

กฎเหล็กข้อที่ 1 ย้าย template ไปไว้ในไฟล์เมื่อ template นั้นมีความยาวเกิน 3 บรรทัด

AppComponent ของเราเป็นคอมโพแนนท์ระดับบนสุด คือใช้แทนหน้าเพจทั้งหน้าครับ

separate-component

จากรูปข้างบนเราจึงกล่าวได้ว่า AppComponent คือหน้าเพจทั้งหมดที่รวม HeaderComponent และ HomeComponent เข้าด้วยกัน

ตอนนี้ก็ถึงเวลาสร้างหน้าโฮมเพจกันละครับ ออกคำสั่งต่อไปนี้เพื่อให้ angular-cli สร้าง HomeComponent ให้กับเรา

Code
1ng generate component home

เข้าไปที่ app/home/home.component.js ครับ เราต้องเจอกับคอมโพแนนท์หน้าตาแบบนี้

TypeScript
1import { Component, OnInit } from '@angular/core'
2
3@Component({
4 selector: 'app-home',
5 templateUrl: 'home.component.html',
6 styleUrls: ['home.component.css'],
7})
8export class HomeComponent implements OnInit {
9 constructor() {}
10
11 ngOnInit() {}
12}

ถึงตรงนี้แล้วต้องขอแนะนำ metadata ของ @Component อีกตัวให้รู้จักนั่นคือ selector เจ้า metadata ตัวนี้เป็นตัวบอกว่าคอมโพแนนท์ของเรานี้จะมีชื่อเป็นอะไรเมื่อไปปรากฎที่ template

จากโค๊ดข้างบนทำให้ทราบว่าคอมโพแนนท์ของเราจะเรียกใช้งานได้บน template ด้วยการอ้างถึง app-home นั่นเอง

ถึงตอนนี้เราคงอยากให้ HomeComponent ของเราไปปรากฎบนหน้าเพจแล้วใช่ไหมครับ เราทราบแล้วว่า AppComponent คือคอมโพแนนท์ผู้ถือครองความเป็นเพจอยู่ เมื่อเราต้องการให้ HomeComponent ไปปรากฎบนหน้าเพจ จึงต้องเรียกใช้งาน HomeComponent บนหน้า AppComponent นั่นเองครับ เปิดไฟล์ app.component.html แล้วแก้ตามนี้ครับ

HTML
1<app-home></app-home>

เราจะรันแอพพลิเคชันของเราขึ้นมากันครับด้วยคำสั่ง ng serve เมื่อทุกอย่างเรียบร้อยให้เข้าไปที่ http://127.0.0.1:4200/

Tada~~~ ตอนนี้จะมีคำว่า home works! ปรากฎขึ้นมาบนหน้าเพจของเราแล้ว ถ้าหน้าเพจของใครเงียบเป็นเป่าสาก ลองตรวจสอบขั้นตอนใหม่อีกครั้งนะครับ

ถึงเวลาเรียนรู้แล้วว่าเกิดอะไรขึ้นบ้างกว่าจะมาเป็น home works! บนหน้าจอของเรา?

หลังเราออกคำสั่ง ng generate component home angular-cli ของเราจะสร้างโฟลเดอร์ชื่อ home ครับ ภายใต้ home ของเรามีโครงสร้างไฟล์ดังนี้

Code
1|--- home
2 |--- shared
3 |--- home.component.html
4 |--- home.component.scss
5 |--- home.component.ts
6 |--- home.component.spec.ts

กฎเหล็กข้อที่ 2 ชื่อไฟล์ใน Angular2 ควรเป็น <ชื่อ>.<ประเภท>.<ชนิดไฟล์> เช่น home.component.html หมายถึงไฟล์ HTML ของคอมโพแนนท์ชื่อ home

ถ้าเราเปิดเข้าไปดูที่ home.component.ts เราจะพบกับสิ่งต่อไปนี้...

TypeScript
1import { Component, OnInit } from '@angular/core'
2
3@Component({
4 // เป็นการบอกว่า หากเราต้องการอ้างถึงคอมโพแนนท์นี้ใน template
5 // ให้เข้าถึงด้วยชื่อ app-home
6 selector: 'app-home',
7 // template ของเราอยู่ที่ home.component.html
8 templateUrl: 'home.component.html',
9 // style ของเราอยู่ที่ home.component.scss
10 // โปรดสังเกตว่าหากเราใช้ SCSS เราสามารถใส่นามสกุลไฟล์เป็น .scss ได้ครับ
11 // angular-cli ไปฝึกยุทธิ์ที่เขาเหลียงซานมาแล้ว เข้าใจหมดทั้ง CSS/LESS/SCSS
12 styleUrls: ['home.component.scss'],
13})
14export class HomeComponent implements OnInit {
15 constructor() {}
16
17 ngOnInit() {}
18}

ถ้าเราแอบแง้มไฟล์ home.component.html ขึ้นมาดู เราก็จะพบกับแท็ก p แสนสวยงามแบบนี้ครับ และนี่หละครับที่มาของ home works! ของเรา

HTML
1<p>
2 home works!
3</p>

นอกเหนือจากการสร้างไฟล์ดังกล่าวข้างต้นแล้ว angular-cli ยังแอบไปเพิ่มอะไรบางอย่างลง app.module.ts ด้วยดังนี้

TypeScript
1import { BrowserModule } from '@angular/platform-browser'
2import { NgModule, ApplicationRef } from '@angular/core'
3import { CommonModule } from '@angular/common'
4import { FormsModule } from '@angular/forms'
5import { AppComponent } from './app.component'
6// ข้าอยู่นี่
7import { HomeComponent } from './home/home.component'
8
9@NgModule({
10 declarations: [
11 AppComponent,
12 HomeComponent, // เราโดน angular-cli ยัดเยียดมาอยู่ตรงนี้หละพวกนาย
13 ],
14 imports: [BrowserModule, CommonModule, FormsModule],
15 providers: [],
16 entryComponents: [AppComponent],
17 bootstrap: [AppComponent],
18})
19export class AppModule {}

คำสั่ง ng g component home จะยัด HomeComponent ของเราลงไปใน declarations ครับ เพื่อให้คอมโพแนนท์ดังกล่าวถูกมองเห็นตลอดทั้งแอพพลิเคชันของเรา ลองเปิดไฟล์ชื่อ app.component.html ดูครับจะพบว่าเราสามารถเรียกใช้ app-home ได้เลยโดยไม่ต้องมีการประกาศซ้ำอีก ด้วยพลานุภาพแห่ง declarations ทำให้ HomeComponent เป็นเสมือนวิญญาณตามติด อยู่ที่ไหนก็พบเจอได้ เรียกได้ ใช้ได้

HTML
1<app-home></app-home>

เราทราบแล้วว่าเมื่อ Angular2 ทำงานจะปลุก AppComponent ขึ้นมา โดยสิ่งที่จะเอาไปแสดงบนหน้าจอก็คือส่วนของเทมเพลตของ AppComponent หรือก็คือสิ่งที่บรรจุใน app.component.html โดยโค๊ดข้างในนั้นได้เรียก app-home ให้แสดงผลอีกทีนึง เหตุนี้ข้อความ home works! ของ HomeComponent ที่บรรจุใน home.component.html จึงทำงานได้

View Encapsulation คืออะไร

ตอนนี้หน้าเพจของเราช่างอุบาทมากครับ เราจะลองใส่ส่วนหัวของเพจพร้อมทั้งใส่ตัวหนังสือกลางจออย่างสวยงาม หน้าจอสุดท้ายจะเป็นเช่นนี้

Home Page

เริ่มจากแก้ไขข้อความใน home.component.html กันซะหน่อยดังนี้

HTML
1<h2 class="title">
2 Welcome to BabelCoder Wiki!
3</h2>

เรามีแท็ก h2 พร้อมระบุคลาสเป็น title เราอยากให้ข้อความนี้ของเราไปอยู่กลางจอเราจึงต้องแก้ไข CSS ของเรากันซะหน่อย เปิด home.component.scss แล้วบรรจงคัดลอกตามนี้ครับ

SASS
1.title {
2 position: fixed;
3 top: 50%;
4 left: 50%;
5 transform: translate(-50%, -50%);
6}

อย่ารอช้าเซฟเลย แล้วเข้าไปดูผลลัพธ์กันที่ http://localhost:4200 ตอนนี้ข้อความของเราก็จะโผล่กลางหน้าจอแล้ว

เรามันเป็นพวกสายฮาร์ดคอร์ครับเลยเปิด Chrome Inspector แล้วจิ้มพรวดเพื่อดูข้อความของเราซะหน่อย และแล้วความจริงก็เปิดเผย...

Local CSS

ดู Angular2 กระทำกับมนุษย์อย่างเราซิ! มันแปลง CSS ที่เราเขียนแบบปกติจาก .title เป็น .title[_ngcontent-lmp-2] หรือแปลเป็นภาษาไทยคือ ให้ประยุกต์สไตล์ต่อไปนี้เฉพาะคลาส title ที่มีแอตทริบิวชื่อ _ngcontent-lmp-2 อยู่เท่านั้น

ปัญหาหนึ่งของการเขียน CSS คือชื่อคลาสชนกัน ในคอมโพแนนท์ Article เราอาจมีคลาส title กำหนดสีหัวข้อบทความเป็นสีเขียว ขณะเดียวกันเราก็มีคอมโพแนนท์ News ที่มีคลาส title กำหนดสีหัวข้อข่าวอันเร่าร้อนด้วยสีแดง คำถามคือถ้าเรามีคลาสชื่อเดียวกันในสองคอมโพแนนท์ แล้วสไตล์ไหนหละที่จะนำไปใช้ สีเขียวหรือสีแดงดี?

Angular2 แก้ปัญหานี้ด้วยการทำ Local CSS ครับ คือจำกัดให้สไตล์ที่เรากำหนดนั้นขึ้นอยู่กับเพียงคอมโพแนนท์เดียว ดังนั้นคลาสอะไรก็ตามที่นิยามไว้ใน home.component.scss ก็จะมีผลเฉพาะ home.component.html เท่านั้น

กลับมาดูที่ title ของเราอีกรอบ Angular2 เพิ่ม _ngcontent-lmp-2 เข้าไปเพื่อทำให้สิ่งนี้แตกต่างจากคลาส title ของคอมโพแนนท์อื่น หากต่อมาเรามีคอมโพแนนท์ชื่อ Page ที่มีการใช้คลาส title เช่นกัน สไตล์จากทั้งสองคอมโพแนนท์ก็จะไม่ชนกันครับ

ความสามารถในการห่อหุ้มสไตล์ไว้กับคอมโพแนนท์เราเรียกว่า View Encapsulation Angular2 อนุญาตให้เรากำหนด View Encapsulation ใน metadata ของคอมโพแนนท์ได้สามแบบคือ

  • Native: จะอาศัย Shadow DOM เข้ามาช่วย
  • Emulated: ถ้าเราไม่ระบุอะไร Angular2 จะหยิบกลยุทธิ์นี้มาใช้ ด้วยการใส่แอตทริบิวต์มั่วๆเข้าไปตามที่เราเห็นครับ เพื่อนๆที่สนใจวิธีการทำงานอ่านเพิ่มเติมได้จากที่นี่
  • None: คือไม่ต้องทำ Local CSS นั่นคือถ้าเราใส่สไตล์ในคอมโพแนนท์นี้ หากมีคอมโพแนนท์อื่นที่ใช้ชื่อคลาสเช่นเดียวกัน สไตล์ก็จะชนกันเลยจ้า

กฎเหล็กข้อที่ 3 ใช้ Local CSS ให้มากที่สุดเท่าที่ทำได้ เพื่อหลีกเลี่ยงการชนกันของ CSS Class

รู้จักโฟลเดอร์ shared

ตอนนี้ก็ได้เวลาสร้างส่วนหัวของเพจกันแล้วครับ แน่นอนว่าส่วนหัวนี้จะตามไปหลอกหลอนเราในทุกๆหน้าเพจ เราจึงจับมันไปอยู่อีกคอมโพแนนท์ และเนื่องจากว่าตัวมันเองต้องแสดงในทุกหน้าเพจ จึงกล่าวได้ว่าคอมโพแนนท์นี้ถูกแชร์โดยเพจต่างๆ ด้วยแบบปฏิบัติที่ดีตามแนวทางแห่ง Angular2 เราจะจับมันไว้ในโฟลเดอร์ชื่อ shared ครับ

Code
1$ ng g component shared/header

กฎเหล็กข้อที่ 4 จงใส่คอมโพแนนท์ที่เป็นตัวแทนของ layout ในหน้าเพจลงในโฟลเดอร์ shared

ผลจากการออกคำสั่งนี้เราจะได้โครงสร้างไฟล์ดังนี้ครับ

Code
1|--- app
2 |--- shared
3 |--- header
4 |--- shared
5 |--- header.component.html
6 |--- header.component.scss
7 |--- header.component.ts
8 |--- header.component.spec.ts
9 |--- index.ts

เอาหละถึงเวลาเพิ่มเทมเพลตของ header กันแล้ว เปิดไฟล์ header.component.html แล้วแก้ตามดังนี้ครับ

HTML
1<header class="header">
2 <nav>
3 <a href="javascript:void(0)" class="brand">Babel Coder Wiki!</a>
4 <ul class="menu">
5 <li class="menu__item">
6 <a href="javascript:void(0)" class="menu__link">
7 All Pages
8 </a>
9 </li>
10 <li class="menu__item">
11 <a href="javascript:void(0)" class="menu__link">
12 About Us
13 </a>
14 </li>
15 </ul>
16 </nav>
17</header>

จากนั้นเราจะใส่สไตล์ให้ header ของเราซะหน่อย ถ้าดูจากรูปจะสังเกตเห็นว่าเรามีการใช้ทั้งสีโทนดำและโทนเขียนใช่ไหมครับ ในการออกแบบเว็บที่ดีเราควรคุมโทนสีไปทิศทางเดียวกัน เราจึงประกาศตัวแปรสีต่างๆที่จะใช้ในเพจของเราที่ app/theme/_variables.scss ดังนี้

SASS
1$gray1-color: #cbcbcb;
2$gray2-color: #e0e0e0;
3
4$dark-gray1-color: #2d3e50;
5
6$green1-color: #18d8a9;
7
8$red1-color: #da4453;
9
10$white-color: #fff;
11$black-color: #000;

สีสันอันสวยงามจะไร้ค่าเมื่อไม่ถูกใช้ เพิ่ม header.component.scss ของเราเพื่อประยุกต์ใช้สไตล์กับสีต่างๆเหล่านี้

SASS
1@import '../../theme/variables';
2
3.header {
4 background: $dark-gray1-color;
5 padding: 1rem;
6 width: 100%;
7 position: fixed;
8 left: 0;
9 top: 0;
10 box-sizing: border-box;
11}
12
13.brand {
14 color: $white-color;
15}
16
17.menu {
18 float: right;
19 display: block;
20 list-style: none;
21 margin: 0;
22 padding: 0;
23
24 &__item {
25 display: inline-block;
26 vertical-align: middle;
27 }
28
29 &__link {
30 padding: 1rem;
31 color: $green1-color;
32 }
33}

ทุกอย่างเรียบร้อย เหลือเพียงอย่างสุดท้ายครับ นั่นคือวิธีนำพาคอมโพแนนท์ header ของเรานี้ไปปรากฎสู่สายตาชาวโลกบนหน้าเพจ

เปิด header.component.ts ของเราดูกันหน่อยครับ จะเห็นเนื้อหาดังนี้

TypeScript
1import { Component, OnInit } from '@angular/core'
2
3@Component({
4 selector: 'app-header',
5 templateUrl: 'header.component.html',
6 styleUrls: ['header.component.scss'],
7})
8export class HeaderComponent implements OnInit {
9 constructor() {}
10
11 ngOnInit() {}
12}

selector เป็นตัวบอกว่าคอมโพแนนท์ header ของเราจะเรียกใช้งานในเทมเพลตของคอมโพแนนท์อื่นได้ด้วยชื่อ app-header

เราทราบแล้วครับว่า Angular2 จะเรียก AppComponent ของเราขึ้นมาทำงานเป็นคอมโพแนนท์แรก ดังนั้นหากเราต้องการให้ app-header ของเราปรากฎตัว เราก็ควรนำมันไปใส่ในเทมเพลตของ AppComponent

เปิด app.component.html ขึ้นมาแล้วยัด app-header ลงไปตามนี้ครับ

HTML
1<app-header></app-header> <app-home></app-home>

กลับไปดูเพจของเราอีกครั้ง... บอกเลยนี่ลุ้นยิ่งกว่าหวยอีก

Home Page

หน้าเพจอันแสนสวยงามของเราก็โผล่ออกมาแล้ว

สรุป

บทความนี้ผมได้แนะนำเพื่อนๆเกี่ยวกับโมดูลและคอมโพแนนท์ใน Angular2 อย่างคร่าวๆ รวมถึงได้รู้จักการใช้งาน Local CSS เพื่อกันไม่ให้สไตล์ของแต่ละคอมโพแนนท์ชนกัน เพื่อนๆสามารถดูซอร์ตโค๊ดของบทความนี้ได้จากที่นี่ครับ

สำหรับบทความหน้าเราจะพูดถึงเรื่องของการจัดการเส้นทางหรือ routing ใน Angular2 กันครับ โปรดติดตาม

สารบัญ

สารบัญ

  • เมื่อ Angular 2 เข้าสู่ยุคของคอมโพแนนท์
  • สร้างโปรเจคกับ angular-cli
  • อธิบายโครงสร้างไฟล์อย่างรวดเร็ว
  • รู้จัก Bootstrap
  • สร้างหน้าโฮมเพจของแอพพลิเคชัน
  • View Encapsulation คืออะไร
  • รู้จักโฟลเดอร์ shared
  • สรุป