บทก่อนหน้าใช้ JavaScript จัดการ DOM และ event โดยตรง เมื่อแอปมีขนาดใหญ่ขึ้น การจัดการไฟล์ โครงสร้างหน้า state และการเรียก API จะซับซ้อนขึ้น Angular จึงช่วยวางโครงแอปให้เป็นระบบผ่าน component, service, router และเครื่องมือพัฒนาอย่าง Angular CLI
คำสำคัญของบทเรียน
เมื่อเว็บมีหลายหน้า หลาย component และต้องติดต่อ API หลายจุด การเขียน JavaScript แบบแยกไฟล์เองทั้งหมดอาจเริ่มดูแลยาก Front-end Framework ช่วยกำหนดรูปแบบการจัดโครงสร้างแอป ทำให้ทีมพัฒนาทำงานร่วมกันง่ายขึ้น
%%{init: {'theme': 'base', 'themeVariables': {
'background': '#282828',
'primaryColor': '#3c3836',
'primaryTextColor': '#fbf1c7',
'primaryBorderColor': '#fabd2f',
'lineColor': '#83a598',
'secondaryColor': '#504945',
'tertiaryColor': '#665c54',
'fontFamily': 'Arial'
}}}%%
flowchart LR
A[HTML/CSS/JS
พื้นฐานเว็บ] --> B[Component
แบ่ง UI เป็นส่วน]
B --> C[Service
แยก logic กลาง]
C --> D[Router
จัดหน้าในแอป]
D --> E[Angular App
แอปที่ดูแลได้เป็นระบบ]
| ปัญหาในแอปใหญ่ | Angular ช่วยอย่างไร |
|---|---|
| HTML และ JavaScript ปนกันมาก | แยกเป็น component |
| logic เรียก API ซ้ำหลายหน้า | แยกเป็น service |
| เปลี่ยนหน้าด้วย reload ทั้งเว็บ | ใช้ router |
| form มี validation หลายเงื่อนไข | ใช้ Angular Forms |
| โค้ดหลายคนเขียนไม่เหมือนกัน | ใช้ CLI และโครงสร้างมาตรฐาน |
Angular CLI คือเครื่องมือ command line สำหรับสร้าง project, component, service, route และ build แอป ช่วยลดงานตั้งค่าเริ่มต้นและทำให้โครงสร้างไฟล์เป็นมาตรฐาน
# สร้างโปรเจกต์ Angular ใหม่
ng new product-dashboard
# เข้าโฟลเดอร์โปรเจกต์
cd product-dashboard
# เปิด development server
ng serve
# สร้าง component สำหรับหน้า products
ng generate component pages/products
# สร้าง service สำหรับจัดการข้อมูลสินค้า
ng generate service services/product
| ไฟล์/โฟลเดอร์ | หน้าที่ |
|---|---|
src/main.ts |
จุดเริ่มต้นของแอป |
src/app |
โค้ดหลักของ application |
*.component.ts |
logic ของ component |
*.component.html |
template ของ component |
*.component.css |
style ของ component |
*.service.ts |
logic กลางหรือการเรียก API |
app.routes.ts |
กำหนดเส้นทางของหน้า |
Component เป็นแนวคิดหลักของ Angular โดยแบ่งหน้าเว็บเป็นชิ้นส่วนย่อยที่มีหน้าที่ชัดเจน เช่น header, sidebar, product list, product card และ footer วิธีนี้ทำให้แก้ไขง่ายและนำกลับมาใช้ซ้ำได้
%%{init: {'theme': 'base', 'themeVariables': {
'background': '#282828',
'primaryColor': '#3c3836',
'primaryTextColor': '#fbf1c7',
'primaryBorderColor': '#fabd2f',
'lineColor': '#b8bb26',
'secondaryColor': '#504945',
'tertiaryColor': '#665c54',
'fontFamily': 'Arial'
}}}%%
flowchart TD
A[AppComponent
โครงหลัก] --> B[HeaderComponent
ส่วนหัว]
A --> C[RouterOutlet
พื้นที่เปลี่ยนหน้า]
C --> D[HomePage
หน้าแรก]
C --> E[ProductsPage
หน้าสินค้า]
E --> F[ProductCard
การ์ดสินค้า]
A --> G[FooterComponent
ส่วนท้าย]
// product-card.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrl: './product-card.component.css'
})
export class ProductCardComponent {
// รับข้อมูลจาก component แม่
@Input() name = '';
@Input() price = 0;
}
<!-- product-card.component.html -->
<article class="product-card">
<h2>{{ name }}</h2>
<p>{{ price }} บาท</p>
</article>
Data Binding คือการเชื่อมข้อมูลระหว่าง component class กับ template ทำให้เมื่อข้อมูลเปลี่ยน หน้าเว็บเปลี่ยนตามได้ง่าย Angular มี binding หลายรูปแบบ เช่น interpolation, property binding, event binding และ two-way binding
| รูปแบบ | Syntax | ใช้เมื่อ |
|---|---|---|
| Interpolation | {{ value }} |
แสดงข้อความ |
| Property Binding | [src]="imageUrl" |
ส่งค่าให้ property ของ element |
| Event Binding | (click)="save()" |
เรียก method เมื่อเกิด event |
| Two-way Binding | [(ngModel)]="keyword" |
เชื่อมค่า form กับตัวแปร |
// products.component.ts
export class ProductsComponent {
title = 'รายการสินค้า';
keyword = '';
search() {
console.log(`ค้นหา: ${this.keyword}`);
}
}
<!-- products.component.html -->
<h1>{{ title }}</h1>
<label for="keyword">คำค้นหา</label>
<input id="keyword" [(ngModel)]="keyword">
<button type="button" (click)="search()">ค้นหา</button>
Directive คือคำสั่งพิเศษใน template ที่ช่วยควบคุม DOM เช่น แสดง/ซ่อน element วนรายการ หรือเปลี่ยน class ตามเงื่อนไข ใน Angular รุ่นใหม่นิยมใช้ control flow syntax เช่น @if และ @for
@if (products.length === 0) {
<p>ยังไม่มีข้อมูลสินค้า</p>
} @else {
<section class="product-grid">
@for (product of products; track product.id) {
<app-product-card
[name]="product.name"
[price]="product.price">
</app-product-card>
}
</section>
}
| คำสั่ง | หน้าที่ | ตัวอย่าง |
|---|---|---|
@if |
แสดงตามเงื่อนไข | แสดงข้อความเมื่อไม่มีข้อมูล |
@for |
วนรายการ | แสดง card ของสินค้า |
[class.active] |
เปลี่ยน class | highlight เมนูที่เลือก |
[disabled] |
เปิด/ปิดการใช้งาน | ปิดปุ่มเมื่อ form ไม่ถูกต้อง |
Service ใช้แยก logic ที่ไม่ควรอยู่ใน component เช่น การเรียก API การจัดการข้อมูล หรือ function ที่ใช้หลายหน้า Angular ใช้ Dependency Injection เพื่อส่ง service เข้าไปให้ component ใช้งาน
%%{init: {'theme': 'base', 'themeVariables': {
'background': '#282828',
'primaryColor': '#3c3836',
'primaryTextColor': '#fbf1c7',
'primaryBorderColor': '#fabd2f',
'lineColor': '#d3869b',
'secondaryColor': '#504945',
'tertiaryColor': '#665c54',
'fontFamily': 'Arial'
}}}%%
flowchart LR
A[ProductsComponent
หน้าแสดงสินค้า] --> B[ProductService
จัดการข้อมูล]
B --> C[HttpClient
เรียก API]
C --> D[Backend API
ข้อมูลสินค้า]
// product.service.ts
import { Injectable } from '@angular/core';
export interface Product {
id: number;
name: string;
price: number;
}
@Injectable({
providedIn: 'root'
})
export class ProductService {
private products: Product[] = [
{ id: 1, name: 'Keyboard', price: 850 },
{ id: 2, name: 'Mouse', price: 420 }
];
getProducts() {
return this.products;
}
}
// products.component.ts
import { Component, inject } from '@angular/core';
import { ProductService } from '../services/product.service';
@Component({
selector: 'app-products',
templateUrl: './products.component.html'
})
export class ProductsComponent {
private productService = inject(ProductService);
products = this.productService.getProducts();
}
Routing ทำให้ Angular app มีหลายหน้า เช่น /, /products, /products/1 โดย browser ไม่ต้อง reload ทั้งเว็บไซต์ ผู้ใช้จึงรู้สึกว่าเปลี่ยนหน้าได้เร็วและต่อเนื่อง
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './pages/home/home.component';
import { ProductsComponent } from './pages/products/products.component';
import { ProductDetailComponent } from './pages/product-detail/product-detail.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'products', component: ProductsComponent },
{ path: 'products/:id', component: ProductDetailComponent }
];
<!-- app.component.html -->
<header>
<a routerLink="/">Home</a>
<a routerLink="/products">Products</a>
</header>
<router-outlet></router-outlet>
| ประเด็น | Traditional Page | Angular Routing |
|---|---|---|
| การเปลี่ยนหน้า | โหลด HTML ใหม่ทั้งหน้า | เปลี่ยน component |
| ความเร็ว | ช้ากว่าเมื่อโหลดซ้ำมาก | เร็วและต่อเนื่อง |
| เหมาะกับ | เว็บไซต์เอกสารธรรมดา | Web application |
| การจัด state | กระจายหลายหน้า | จัดในแอปเดียว |
Angular รองรับการสร้าง form หลายแบบ ในบทนี้เริ่มจาก template-driven form เพื่อเข้าใจพื้นฐานการผูกค่าระหว่าง input กับตัวแปร และการตรวจสอบค่าก่อนส่งข้อมูล
<form #productForm="ngForm" (ngSubmit)="save()">
<label for="name">ชื่อสินค้า</label>
<input
id="name"
name="name"
[(ngModel)]="productName"
required
minlength="3">
<label for="price">ราคา</label>
<input
id="price"
name="price"
type="number"
[(ngModel)]="price"
required
min="1">
<button type="submit" [disabled]="productForm.invalid">
บันทึก
</button>
</form>
| Validation | ความหมาย |
|---|---|
required |
ต้องกรอกข้อมูล |
minlength |
ความยาวขั้นต่ำ |
maxlength |
ความยาวสูงสุด |
min |
ค่าต่ำสุด |
max |
ค่าสูงสุด |
เมื่อต้องเชื่อมต่อ backend จริง Angular ใช้ HttpClient เพื่อส่ง request และรับข้อมูลจาก API โดยมักวาง logic ไว้ใน service แล้วให้ component เรียกใช้ service อีกที
// product-api.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ProductApiService {
private http = inject(HttpClient);
getProducts() {
return this.http.get('/api/products');
}
}
Mini project สำหรับบทนี้คือ Product Dashboard ประกอบด้วยหน้า Home, Products และ Product Detail พร้อม component อย่างน้อย 3 ส่วน
src/app/
pages/
home/
products/
product-detail/
components/
header/
product-card/
loading-state/
services/
product.service.ts
app.routes.ts
| ส่วน | รายละเอียด |
|---|---|
| Home Page | อธิบายภาพรวมระบบ |
| Products Page | แสดงรายการสินค้าเป็น card |
| Product Detail | แสดงรายละเอียดจาก id ใน route |
| Product Service | เก็บข้อมูลหรือเรียก API |
| Navigation | ใช้ routerLink เปลี่ยนหน้า |
| Form | เพิ่มช่องค้นหาหรือฟอร์มเพิ่มสินค้า |
| รายการตรวจ | คำถามที่ต้องตอบ |
|---|---|
| Component | แบ่ง UI เป็น component ที่หน้าที่ชัดเจนหรือไม่ |
| Template | ใช้ binding และ directive ถูกต้องหรือไม่ |
| Service | logic เรียกข้อมูลไม่ปนใน template หรือไม่ |
| Routing | มี route อย่างน้อย 2 หน้าและใช้ router-outlet หรือไม่ |
| Form | มี input, validation และปุ่ม submit หรือไม่ |
| HTTP/API | เตรียม service สำหรับเรียก API หรือข้อมูลจำลองหรือไม่ |
| Maintainability | ตั้งชื่อไฟล์ class และ selector อ่านง่ายหรือไม่ |
สร้าง Angular app ชื่อ Product Dashboard โดยมีเงื่อนไข:
@for หรือ *ngFor เพื่อวนรายการ