State Management ด้วย NgRx เบื้องต้น (Action, Reducer, Selector)

NgRx คือแนวทางจัดการ State ของ Angular โดยใช้แนวคิด Store กลาง ทำให้ข้อมูลสำคัญของแอปมี Single Source of Truth และติดตามการเปลี่ยนแปลงผ่าน Action ได้ชัดเจน

Timeline/ประวัติศาสตร์

%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#fabd2f", "primaryTextColor": "#282828", "primaryBorderColor": "#b57614", "lineColor": "#7c6f64", "secondaryColor": "#83a598", "tertiaryColor": "#b8bb26", "background": "#fbf1c7", "mainBkg": "#ebdbb2", "fontFamily": "Tahoma, sans-serif"}}}%%
flowchart LR
  subgraph Era1["ยุค Local State / Component State"]
    A["ข้อมูลอยู่หลาย Component
Debug ยาก"] end subgraph Era2["ยุค Store Pattern / Central Store"] B["Store
Single Source of Truth"] C["Action + Reducer
เปลี่ยน State แบบคาดเดาได้"] end subgraph Era3["ยุค Side Effects / Reactive State"] D["Effect
จัดการ HTTP"] E["Selector
ดึงข้อมูลเพื่อแสดงผล"] end A --> B --> C --> D --> E

แนวคิดสำคัญ

ตารางเปรียบเทียบองค์ประกอบ NgRx

องค์ประกอบ หน้าที่ ตัวอย่าง
Store เก็บ State กลาง users, loading
Action บอกว่าเกิดอะไรขึ้น loadUsers
Reducer สร้าง State ใหม่ set loading true
Selector อ่าน State selectAllUsers
Effect จัดการ API call HttpClient

Mermaid Diagram: NgRx Data Flow

%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#fabd2f", "primaryTextColor": "#282828", "primaryBorderColor": "#b57614", "lineColor": "#7c6f64", "secondaryColor": "#83a598", "tertiaryColor": "#b8bb26", "background": "#fbf1c7", "mainBkg": "#ebdbb2", "fontFamily": "Tahoma, sans-serif"}}}%%
flowchart LR
  A["Component
หน้าจอ"] -->|dispatch Action| B["Action
เหตุการณ์"] B --> C["Reducer
สร้าง State ใหม่"] C --> D["Store
State กลาง"] D -->|select Selector| A B --> E["Effect
เรียก API"] E --> F["HTTP API
Server"] F --> E -->|success/failure Action| B

Code Example

// user.actions.ts
// Action อธิบายเหตุการณ์ในระบบ
import { createAction, props } from '@ngrx/store';

export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction(
  '[User] Load Users Success',
  props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
  '[User] Load Users Failure',
  props<{ error: string }>()
);
// user.reducer.ts
// Reducer ต้องเป็น pure function และคืน state ใหม่เสมอ
import { createReducer, on } from '@ngrx/store';
import { loadUsers, loadUsersFailure, loadUsersSuccess } from './user.actions';

export interface UserState {
  users: User[];
  loading: boolean;
  error: string | null;
}

export const initialState: UserState = {
  users: [],
  loading: false,
  error: null
};

export const userReducer = createReducer(
  initialState,
  on(loadUsers, state => ({ ...state, loading: true })),
  on(loadUsersSuccess, (state, { users }) => ({ ...state, users, loading: false })),
  on(loadUsersFailure, (state, { error }) => ({ ...state, error, loading: false }))
);

// ตัวอย่างการใช้งาน:
// this.store.dispatch(loadUsers());
// user.selectors.ts
// Selector ดึงข้อมูลเฉพาะส่วนที่ Component ต้องใช้
import { createFeatureSelector, createSelector } from '@ngrx/store';

export const selectUserState = createFeatureSelector<UserState>('users');
export const selectAllUsers = createSelector(selectUserState, state => state.users);
export const selectUsersLoading = createSelector(selectUserState, state => state.loading);

กิจกรรมท้ายบท

  1. ออกแบบ State สำหรับรายการบทเรียน
  2. สร้าง Action loadLessons
  3. สร้าง Reducer สำหรับ loading และ success
  4. สร้าง Selector เพื่อดึงรายการบทเรียน

กลับสัปดาห์ที่ 9