Section 3

Angular พื้นฐาน — สร้างหน้าเว็บชิ้นแรก
Part 1 — รู้จัก Angular
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- 🟢 Angular (frontend) ← กำลังทำส่วนนี้
- ✅ NestJS (backend) (ทำไปแล้ว)
- ✅ PostgreSQL (ฐานข้อมูล) (ทำไปแล้ว)
ต่อไป: ใช้ signal แสดงรายการหนังสือแบบไดนามิก
1.1 ติดตั้ง Angular CLI
npm install -g @angular/cli@22
💡 ทำไมต้องติดตั้งอีกตัว เครื่องมือสร้างโปรเจกต์ NestJS ที่ใช้ใน Section 1 (
@nestjs/cli) กับเครื่องมือสร้างโปรเจกต์ Angular เป็นคนละตัวกัน ต้องมีทั้งคู่
✅ ตรวจสอบ
ng version
ต้องเห็นเลขเวอร์ชัน Angular CLI: 22.x.x ขึ้นมา
1.2 สร้างโปรเจกต์ Angular
cd C:\webdev\booklab\apps
ng new web --directory web --style tailwind --routing true --ssr false --skip-git --skip-install --defaults
💡 ความหมายของแต่ละตัวเลือก
--directory web= สร้างในโฟลเดอร์ชื่อweb(ให้เข้าชุดกับapps/apiที่มีอยู่แล้ว)--style tailwind= ใช้ Tailwind CSS ตั้งแต่ตอนสร้างโปรเจกต์เลย Angular จะติดตั้งและตั้งค่าไฟล์ที่จำเป็นให้อัตโนมัติทั้งหมด ไม่ต้องมาตั้งเองทีหลัง--routing true= เตรียมระบบเปลี่ยนหน้า (Router) ไว้ให้ตั้งแต่ต้น จะได้ใช้ใน Section 5--ssr false= ปิดโหมด Server-Side Rendering (วิชานี้ไม่ใช้)--skip-git= ไม่ต้องสร้าง Git repo--skip-install= ยังไม่ต้องติดตั้ง package ตอนนี้ จะติดตั้งรวมจาก root เช่นเดียวกับตอนสร้าง NestJS ใน Section 1--defaults= ตอบคำถามอื่น ๆ ทั้งหมดด้วยค่าเริ่มต้น ไม่ต้องตอบทีละข้อ
✅ ต้องเห็น โฟลเดอร์ apps/web เกิดขึ้น พร้อมไฟล์ apps/web/.postcssrc.json และ apps/web/src/styles.css ที่มีบรรทัด @import 'tailwindcss'; อยู่แล้ว (Angular สร้างให้เองตาม --style tailwind ที่ระบุไว้)
1.3 ติดตั้งทุกอย่างจาก root
cd C:\webdev\booklab
npm install
ใช้เวลาสักครู่ เพราะ Angular มีเครื่องมือค่อนข้างมาก (รวมถึง Tailwind ที่ติดมาด้วยในคำสั่งก่อนหน้า)
💡 สังเกตว่าตอนนี้โปรเจกต์เราใช้ TypeScript สองเวอร์ชันพร้อมกัน เพราะ Angular 22 ต้องการ TypeScript รุ่นใหม่กว่า NestJS 11 ที่ทำไปใน Section 1 ต้องการ npm ฉลาดพอที่จะจัดการให้แต่ละฝั่งได้เวอร์ชันที่ถูกต้องอัตโนมัติ ไม่ต้องทำอะไรเพิ่ม แค่รู้ไว้เฉย ๆ ว่าทำไมบางทีถึงเห็นโฟลเดอร์
typescriptซ้อนอยู่ทั้งที่ root และในapps/web/node_modules
✅ ต้องเห็น บรรทัดท้ายบอกจำนวน package ที่ติดตั้งเพิ่ม
1.4 กัน VS Code ขีดเส้นแดงหลอก
โปรเจกต์เรามี TypeScript สองเวอร์ชัน (อธิบายไว้ในขั้นที่ 1.3) บางครั้ง VS Code อาจเลือกใช้เวอร์ชันผิดกับไฟล์ Angular แล้วขีดเส้นแดงทั้งที่โค้ดไม่ได้ผิด ป้องกันไว้ก่อนเลยด้วยการสร้างโฟลเดอร์ .vscode ที่ root ของโปรเจกต์ (C:\webdev\booklab\.vscode) แล้วสร้างไฟล์ settings.json ข้างใน
{
"typescript.tsdk": "apps/web/node_modules/typescript/lib"
}
บันทึกแล้วกด Ctrl + Shift + P พิมพ์ TypeScript: Select TypeScript Version เลือก Use Workspace Version
💡 ไฟล์นี้บอก VS Code ว่า "ให้ใช้ TypeScript เวอร์ชันที่อยู่ใน
apps/webเป็นตัวหลักในการตรวจโค้ด" ซึ่งตรงกับเวอร์ชันที่ Angular ต้องการพอดี
1.5 รัน frontend ครั้งแรก
เปิด Terminal ใหม่ (ปล่อยตัวที่รัน API ทิ้งไว้)
cd C:\webdev\booklab
npm run dev:web
รอสักครู่แล้วเปิด http://localhost:4200
✅ ต้องเห็นหน้าเว็บต้อนรับของ Angular (โลโก้และลิงก์ต่าง ๆ ที่ Angular ใส่มาให้เป็นค่าเริ่มต้น) — Section นี้เราจะลบออกแล้วเขียนของตัวเอง
💡 ตอนนี้เรามีโปรแกรมสองตัวรันพร้อมกัน
- port 3000 = backend NestJS
- port 4200 = frontend Angular
เป็นคนละโปรแกรม คนละ Terminal หากปิดตัวใด ตัวนั้นจะหยุดทำงานทันที

⚠️ ถ้าจะรันทั้งสองด้วยคำสั่งเดียว ใช้
npm run devแต่ตอนเรียนแนะนำให้แยก Terminal จะดูข้อความ error ง่ายกว่า
1.6 สำรวจไฟล์
apps/web/src/app/
├── app.ts ← โค้ดของหน้าหลัก
├── app.html ← หน้าตาของหน้าหลัก (ตอนนี้ยังเป็นหน้าต้อนรับของ Angular)
├── app.css ← สไตล์เฉพาะของหน้าหลัก
├── app.config.ts ← ตั้งค่ารวมของทั้งแอป
└── app.routes.ts ← ตาราง route ของ URL
เปิด app.ts ดู
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
💡 Component คืออะไร — แนวคิดหลักของ Angular คือตัวต่อเลโก้ ชิ้นหนึ่ง ที่รวม 3 อย่างไว้ด้วยกัน
.html= หน้าตา.css= สไตล์.ts= สมองที่ควบคุมพฤติกรรมการ์ดหนังสือหนึ่งใบคือหนึ่งชิ้น ทำแม่พิมพ์ไว้ครั้งเดียว แล้วปั๊มออกมา 20 ใบใส่ข้อมูลคนละเล่มได้ หน้าเว็บทั้งหน้าคือเลโก้หลายชิ้นประกอบกัน
💡
@Component({...})= ป้ายที่บอกว่าคลาสนี้เป็น component พร้อมบอกว่าไฟล์หน้าตาอยู่ไหน 💡selector: 'app-root'= ชื่อแท็กที่เอาไปวางใน HTML ได้ เหมือน<div>แต่เป็นของเราเอง
1.7 Tailwind CSS เบื้องต้น
ตอนสร้างโปรเจกต์ใน 1.2 เราใส่ --style tailwind ไว้ Angular เลยติดตั้ง Tailwind CSS มาให้เรียบร้อยแล้ว หัวข้อถัดไปกำลังจะได้ใช้จริง ก่อนจะพิมพ์ตามควรรู้ก่อนว่าที่พิมพ์ไปคืออะไร
เขียน CSS แบบเดิม เทียบกับ Tailwind
แบบเดิม ต้องคิดชื่อคลาสเอง แล้วไปเขียนกฎในไฟล์ CSS แยกอีกที
<h1 class="page-title">Booklab</h1>
<!-- แล้วต้องไปเขียนอีกไฟล์หนึ่ง -->
.page-title {
font-size: 1.875rem;
font-weight: 700;
color: #2563eb;
padding: 2rem;
}
แบบ Tailwind ใช้คลาสสำเร็จรูปที่มีอยู่แล้ว ใส่ในแท็กได้เลย ไม่ต้องเปิดไฟล์ CSS
<h1 class="text-3xl font-bold text-blue-600 p-8">Booklab</h1>
💡 หนึ่งคลาส = หนึ่งหน้าที่ แนวคิดนี้เรียกว่า utility-first แทนที่จะสร้างคลาสใหญ่หนึ่งตัวที่ทำหลายอย่าง Tailwind ให้คลาสเล็ก ๆ ที่ทำอย่างเดียวมาต่อกัน อยากได้หน้าตาแบบไหนก็หยิบมาประกอบ
ถอดรหัสทีละตัว
บรรทัดข้างบนคือบรรทัดเดียวกับที่จะพิมพ์ในหัวข้อถัดไป แกะดูทีละคลาส
| คลาส | แปลว่า |
|---|---|
text-3xl |
ขนาดตัวอักษรใหญ่ระดับ 3xl |
font-bold |
ตัวหนา |
text-blue-600 |
สีตัวอักษรเป็นน้ำเงินระดับ 600 |
p-8 |
ระยะห่างด้านในรอบ ๆ ขนาด 8 (= 32px) |
💡 หลักการอ่านชื่อคลาส เกือบทุกตัวอ่านได้เป็น
{สิ่งที่จะปรับ}-{ขนาด}เช่นpคือ paddingmคือ margintextคือตัวอักษรbgคือสีพื้นหลัง พอจับหลักได้จะเดาคลาสใหม่ ๆ ได้เองโดยไม่ต้องเปิดคู่มือ
ตารางคลาสที่จะเจอบ่อยใน Section 3 ถึง 6
| กลุ่ม | คลาสที่จะเจอจริง |
|---|---|
| ระยะห่างด้านใน (padding) | p-4 p-6 p-8 px-3 py-2 |
| ระยะห่างด้านนอก (margin) | mt-2 mb-4 mx-auto |
| ตัวอักษร | text-xs text-sm text-lg text-3xl font-bold |
| สี | text-gray-600 text-white bg-blue-600 bg-gray-100 |
| กรอบและมุม | border rounded rounded-lg shadow-md |
| การจัดวาง | flex grid gap-3 items-center w-full max-w-3xl |
ไม่ต้องท่องจำ เปิดกลับมาดูตารางนี้ได้ตลอด เมื่อใช้บ่อยขึ้นจะจำได้เอง
💡 ระบบตัวเลขของขนาด เลข 4 เท่ากับ 1rem หรือ 16px ดังนั้น
p-2= 8pxp-4= 16pxp-8= 32px เลขยิ่งมากยิ่งห่าง💡 ระบบตัวเลขของสี ไล่ตั้งแต่ 50 ถึง 900 เลขยิ่งมากยิ่งเข้ม
bg-blue-100คือฟ้าอ่อน ส่วนtext-blue-600คือน้ำเงินเข้มพอที่จะอ่านบนพื้นขาวได้สบายตา
⚠️ พิมพ์ชื่อคลาสผิดจะเงียบสนิท ไม่มี error ขึ้นที่ไหนเลย หน้าเว็บก็ยังรันได้ปกติ เพียงแต่สไตล์จะไม่แสดงผล หากใส่คลาสแล้วหน้าตาไม่เปลี่ยน ให้สงสัยเรื่องสะกดผิดก่อนเสมอ เช่น
text-grey-600ต้องเป็นtext-gray-600(สะกดแบบอเมริกัน)
ลองเดาดู
ถ้าเห็นแท็กนี้ คิดว่าผลลัพธ์จะออกมาหน้าตาแบบไหน
<p class="p-2 text-sm text-red-600">ไม่พบข้อมูล</p>
เฉลย: ข้อความ "ไม่พบข้อมูล" ตัวเล็กกว่าปกติ สีแดง และมีระยะห่างด้านในรอบ ๆ 8px — คลาสชุดนี้จะได้ใช้จริงตอนทำสถานะ "ไม่พบข้อมูล" ใน Section 4
1.8 แทนที่หน้าเริ่มต้นด้วยของเราเอง
หน้าที่เห็นตอนนี้เป็นหน้าต้อนรับที่ Angular ใส่มาให้ ลบทิ้งแล้วใส่ของเราเอง แทนที่ทั้งไฟล์ app.html — คลาสในโค้ดข้างล่างคือชุดที่เพิ่งแกะกันไปใน 1.7
<h1 class="text-3xl font-bold text-blue-600 p-8">Booklab</h1>
<router-outlet />
กด Ctrl + S
✅ หน้าเว็บต้องเปลี่ยนเองทันทีโดยไม่ต้องกด refresh เหลือแค่คำว่า "Booklab" ตัวใหญ่สีน้ำเงิน
💡 เรียกว่า hot reload — Angular คอยจับตาไฟล์อยู่ พอบันทึกก็อัปเดตหน้าจอให้เลย
💡
<router-outlet />= ช่องว่างที่บอกว่า "เอาหน้าที่ตรงกับ URL ปัจจุบันมาแสดงตรงนี้" จำเป็นต้องมีไว้เสมอ และจะได้ใช้จริงใน Section 5 ตอนทำหลายหน้า
Part 2 — Signal และการแสดงข้อมูล
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- 🟢 Angular (frontend) ← กำลังทำส่วนนี้
- ✅ NestJS (backend) (ทำไปแล้ว)
- ✅ PostgreSQL (ฐานข้อมูล) (ทำไปแล้ว)
ต่อไป: แตกโค้ดออกเป็น component ย่อยที่ใช้ซ้ำได้
2.1 สร้าง component รายการหนังสือ
cd apps/web
npm exec -- ng generate component books/book-list --skip-tests
✅ ต้องเกิด 3 ไฟล์ ใน src/app/books/book-list/
⚠️ สังเกตว่าชื่อคลาสคือ
BookListไม่มีคำว่า Component ต่อท้าย และชื่อไฟล์ไม่มี.component— เป็นรูปแบบใหม่ของ Angular เวอร์ชันนี้ ถ้าไปเจอบทเรียนเก่าที่เขียนว่าbook-list.component.tsก็คือของเดิม ใช้แทนกันได้
2.2 สร้าง Book Interface
สร้างไฟล์ apps/web/src/app/books/book.ts
export interface Book {
id: number;
title: string;
author: string;
year: number;
category?: { id: number; name: string };
}
💡 เหมือน interface ที่เขียนฝั่ง API เป๊ะ ๆ เพราะข้อมูลชุดเดียวกันวิ่งข้ามมา 💡 เครื่องหมาย
?ที่category?แปลว่า "มีหรือไม่มีก็ได้"
2.3 ใส่ข้อมูลจำลองและ signal
แทนที่ apps/web/src/app/books/book-list/book-list.ts
import { Component, signal } from '@angular/core';
import { Book } from '../book';
@Component({
selector: 'app-book-list',
imports: [],
templateUrl: './book-list.html',
styleUrl: './book-list.css',
})
export class BookList {
books = signal<Book[]>([
{ id: 1, title: 'Clean Code', author: 'Robert C. Martin', year: 2008 },
{ id: 2, title: 'Refactoring', author: 'Martin Fowler', year: 1999 },
{ id: 3, title: "Don't Make Me Think", author: 'Steve Krug', year: 2000 },
]);
}
💡 signal คืออะไร — และทำไมสำคัญ คือกล่องที่ส่งเสียงได้ เมื่อค่าข้างในเปลี่ยน signal จะแจ้งไปยังหน้าจอเอง แล้วหน้าจออัปเดตตามทันที โดยที่เราไม่ต้องสั่งให้วาดใหม่เอง
signal<Book[]>([...])= สร้างกล่อง ใส่รายการหนังสือไว้books()มีวงเล็บ = เปิดกล่องดูค่าbooks.set(ของใหม่)= เปลี่ยนค่าในกล่อง⚠️ ต้องมีวงเล็บทุกครั้งที่อ่านค่า ลืมใส่แล้วจะได้ตัวกล่องแทนที่จะได้ของข้างใน เป็นจุดที่ผิดพลาดบ่อยที่สุด
2.4 แสดงผลบนหน้าจอ
แทนที่ book-list.html
<div class="p-6">
<h1 class="text-2xl font-bold mb-4">รายการหนังสือ</h1>
<ul class="grid gap-3">
@for (b of books(); track b.id) {
<li class="border rounded-lg p-4">
<p class="font-bold">{{ b.title }}</p>
<p class="text-sm text-gray-600">{{ b.author }} · {{ b.year }}</p>
</li>
}
</ul>
</div>
💡
@for (...) { }= คำสั่งวนซ้ำที่เขียนได้ใน HTML โดยตรง แปลว่า "หยิบหนังสือทีละเล่มจากbooks()มาสร้าง<li>หนึ่งอัน"💡
track b.id= บอก Angular ว่าใช้idเป็นตัวแยกแยะว่าอันไหนเป็นอันไหน เพื่อให้อัปเดตหน้าจอได้เร็ว จำเป็นต้องใส่ ถ้าไม่ใส่จะ error💡
{{ b.title }}= เอาค่าจากโค้ดมาแสดงบนหน้าจอ เรียกว่า binding หรือเส้นเชื่อมระหว่างข้อมูลกับสิ่งที่เห็น💡
class="p-6 text-2xl font-bold"= คลาสของ Tailwind ที่ติดตั้งไว้แล้ว แต่ละตัวทำหน้าที่เดียว เช่นp-6คือ padding,font-boldคือตัวหนา — ไม่ต้องเขียน CSS เองเลย
2.5 เอา component มาแสดงจริง
แก้ apps/web/src/app/app.routes.ts
import { Routes } from '@angular/router';
import { BookList } from './books/book-list/book-list';
export const routes: Routes = [
{ path: '', component: BookList },
];
แก้ apps/web/src/app/app.html ให้เหลือแค่
<router-outlet />
💡
<router-outlet />= ช่องว่างที่บอกว่า "เอาหน้าที่ตรงกับ URL ปัจจุบันมาแสดงตรงนี้" 💡path: ''= หน้าแรกสุด คือlocalhost:4200โดยไม่มีเส้นทางต่อท้าย
✅ เปิด localhost:4200 ต้องเห็นรายการหนังสือ 3 เล่มในกรอบสวย ๆ
Part 3 — แตก component ย่อย
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- 🟢 Angular (frontend) ← กำลังทำส่วนนี้
- ✅ NestJS (backend) (ทำไปแล้ว)
- ✅ PostgreSQL (ฐานข้อมูล) (ทำไปแล้ว)
ต่อไป: เพิ่มปุ่มกดโต้ตอบกับผู้ใช้
ตอนนี้โค้ดหน้าตาของการ์ดปนอยู่ในรายการ ถ้าอยากใช้การ์ดที่อื่นด้วยจะคัดลอกไปมา ลองแยกออกมาเป็นชิ้นเลโก้ของตัวเอง
3.1 สร้าง component การ์ด
npm exec -- ng generate component books/book-card --skip-tests
3.2 ให้การ์ดรับข้อมูลเข้ามาได้
แทนที่ book-card.ts
import { Component, input } from '@angular/core';
import { Book } from '../book';
@Component({
selector: 'app-book-card',
imports: [],
templateUrl: './book-card.html',
styleUrl: './book-card.css',
})
export class BookCard {
book = input.required<Book>();
}
💡
input()คือช่องเสียบข้อมูลของตัวต่อ แม่พิมพ์การ์ดมีช่องให้เสียบว่าจะแสดงหนังสือเล่มไหน ชิ้นเดียวกันจึงแสดงข้อมูลต่างกันได้
input.required<Book>()= ช่องนี้ต้องเสียบ ถ้าลืมจะเตือนตั้งแต่ตอนพิมพ์ อ่านค่าด้วยbook()มีวงเล็บ เหมือน signal เพราะเป็น signal ชนิดหนึ่ง
3.3 หน้าตาของการ์ด
แทนที่ book-card.html
<div class="border rounded-lg p-4 hover:shadow-md transition">
<p class="font-bold text-lg">{{ book().title }}</p>
<p class="text-sm text-gray-600">{{ book().author }}</p>
<div class="mt-2 flex items-center gap-2">
<span class="text-xs bg-gray-100 px-2 py-1 rounded">{{ book().year }}</span>
@if (book().category) {
<span class="text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded">
{{ book().category?.name }}
</span>
}
</div>
</div>
💡
@if (...) { }= แสดงเฉพาะเมื่อเงื่อนไขเป็นจริง ตรงนี้คือ "ถ้ามีหมวดหมู่ค่อยแสดงป้ายสีฟ้า" 💡?.ในcategory?.name= "ถ้ามี category ค่อยหยิบ name" ป้องกัน error ตอนไม่มีข้อมูล
3.4 เอาการ์ดไปใช้ในรายการ
แก้ book-list.ts เพิ่ม import
import { Component, signal } from '@angular/core';
import { Book } from '../book';
import { BookCard } from '../book-card/book-card';
@Component({
selector: 'app-book-list',
imports: [BookCard],
templateUrl: './book-list.html',
styleUrl: './book-list.css',
})
export class BookList {
books = signal<Book[]>([
{ id: 1, title: 'Clean Code', author: 'Robert C. Martin', year: 2008, category: { id: 1, name: 'Programming' } },
{ id: 2, title: 'Refactoring', author: 'Martin Fowler', year: 1999, category: { id: 1, name: 'Programming' } },
{ id: 3, title: "Don't Make Me Think", author: 'Steve Krug', year: 2000, category: { id: 2, name: 'Design' } },
]);
}
⚠️ ต้องใส่
BookCardในimports: []มิฉะนั้น Angular จะไม่รู้จักแท็ก<app-book-card>— เป็นจุดที่มักลืมกันบ่อย
แก้ book-list.html
<div class="p-6 max-w-3xl mx-auto">
<h1 class="text-2xl font-bold mb-4">รายการหนังสือ</h1>
<div class="grid gap-3">
@for (b of books(); track b.id) {
<app-book-card [book]="b" />
}
</div>
</div>
💡
[book]="b"= เสียบข้อมูลเข้าช่องbookของการ์ด วงเล็บเหลี่ยมแปลว่า "ค่านี้มาจากโค้ด" ถ้าไม่ใส่วงเล็บ Angular จะคิดว่าเราส่งข้อความคำว่า "b" ไปจริง ๆ
✅ หน้าเว็บต้องเหมือนเดิม แต่ตอนนี้โค้ดแยกเป็นชิ้นแล้ว
Part 4 — เพิ่มการโต้ตอบ
🗺️ ตำแหน่งตอนนี้ในระบบ
- ✅ เตรียมเครื่องมือ (ทำไปแล้ว)
- 🟢 Angular (frontend) ← กำลังทำส่วนนี้
- ✅ NestJS (backend) (ทำไปแล้ว)
- ✅ PostgreSQL (ฐานข้อมูล) (ทำไปแล้ว)
ต่อไป: Section หน้า: เชื่อม frontend นี้เข้ากับ API จริง
4.1 ทำปุ่มกรองตามหมวดหมู่
แก้ book-list.ts เพิ่มสองอย่าง
export class BookList {
books = signal<Book[]>([ /* ...ของเดิม... */ ]);
selected = signal<string>('ทั้งหมด');
filtered() {
const all = this.books();
if (this.selected() === 'ทั้งหมด') return all;
return all.filter((b) => b.category?.name === this.selected());
}
}
แก้ book-list.html
<div class="p-6 max-w-3xl mx-auto">
<h1 class="text-2xl font-bold mb-4">รายการหนังสือ</h1>
<div class="flex gap-2 mb-4">
@for (c of ['ทั้งหมด', 'Programming', 'Design']; track c) {
<button
(click)="selected.set(c)"
class="px-3 py-1 rounded border"
[class.bg-blue-600]="selected() === c"
[class.text-white]="selected() === c">
{{ c }}
</button>
}
</div>
<div class="grid gap-3">
@for (b of filtered(); track b.id) {
<app-book-card [book]="b" />
}
</div>
</div>
💡
(click)="..."= วงเล็บกลมคือเหตุการณ์ แปลว่า "เมื่อคลิกให้ทำสิ่งนี้" จำง่าย ๆ: วงเล็บเหลี่ยม[ ]= ข้อมูลไหลเข้าหน้าจอ / วงเล็บกลม( )= เหตุการณ์ไหลออกจากหน้าจอ💡
selected.set(c)= เปลี่ยนค่าในกล่อง signal พอเปลี่ยนปุ๊บ หน้าจอวาดใหม่เองทันที นี่คือพลังของ signal💡
[class.bg-blue-600]="เงื่อนไข"= ใส่คลาสนี้เฉพาะเมื่อเงื่อนไขเป็นจริง ใช้ทำปุ่มที่ถูกเลือกให้เป็นสีน้ำเงิน
✅ กดปุ่มแล้วรายการต้องเปลี่ยนตาม และปุ่มที่เลือกต้องเป็นสีน้ำเงิน
🚀 ถ้าเสร็จก่อนเพื่อน
- เพิ่มตัวเลขบอกจำนวนเล่มที่กำลังแสดงอยู่
- เพิ่มปุ่มเรียงตามปี จากใหม่ไปเก่า
- ทำให้การ์ดเปลี่ยนสีขอบเมื่อหนังสือเก่ากว่าปี 2000
❓ ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
'app-book-card' is not a known element |
ลืมใส่ใน imports: [] |
เพิ่ม BookCard เข้าไป |
แสดงคำว่า [object Object] หรือฟังก์ชัน |
ลืมวงเล็บตอนอ่าน signal | เปลี่ยน books เป็น books() |
@for ขึ้น error |
ลืม track |
เพิ่ม track b.id |
| หน้าขาวเปล่า | มี error ใน console | กด F12 เปิด Console อ่านข้อความสีแดง |
| Tailwind ไม่ทำงาน | พิมพ์ชื่อคลาสผิด | ตรวจสะกด เช่น text-gray-600 ไม่ใช่ text-grey-600 |
📌 What You Learned
| Syntax | Meaning |
|---|---|
signal(value) |
A reactive container that holds a value. |
name() |
Read the current value. |
name.set(newValue) |
Update the stored value. |
input.required<T>() |
A required input received from a parent component. |
@for (x of list; track x.id) |
Render each item in a list. |
@if (condition) |
Render content when a condition is true. |
[prop]="value" |
Pass data into a property. |
(click)="handler()" |
Handle an event. |
Next section: Remove the mock data and load real data from the API created in Section 2.