Angular 專案架構建議

在專案中使用 NgRx 進行狀態管理,

並且清晰地分離不同的模塊,

使代碼更具模塊化和可維護性

項目目錄結構

src/
├── app/
│   ├── core/
│   │   ├── guards/               # 守衛
│   │   ├── interceptors/         # 攔截器
│   │   ├── services/             # 全域服務
│   │   ├── models/               # 全域模型
│   │   ├── config/               # 配置
│   │   ├── utils/                # 工具函數
│   │   ├── core.module.ts        # 核心模組
│   │   └── index.ts              # 導出核心模組內容
│   ├── shared/
│   │   ├── components/           # 共享組件
│   │   ├── pipes/                # 共享管道
│   │   ├── directives/           # 共享指令
│   │   ├── services/             # 共享服務
│   │   ├── shared.module.ts      # 共享模組
│   │   └── index.ts              # 導出共享模組內容
│   ├── features/
│   │   ├── auth/                # 認證模組
│   │   │   ├── login/           # 登入功能
│   │   │   ├── register/        # 註冊功能
│   │   │   ├── state/           # NgRx狀態管理
│   │   │   │   ├── actions/     # Actions
│   │   │   │   ├── effects/     # Effects
│   │   │   │   ├── reducers/    # Reducers
│   │   │   │   ├── selectors/   # Selectors
│   │   │   │   └── index.ts     # 導出NgRx狀態管理內容
│   │   │   ├── auth.service.ts  # 認證服務
│   │   │   ├── auth.guard.ts    # 認證守衛
│   │   │   ├── auth.module.ts   # 認證模組
│   │   │   └── auth-routing.module.ts  # 認證模組路由
│   │   ├── user/                # 用戶管理模組
│   │   │   ├── components/      # 用戶管理相關組件
│   │   │   ├── services/        # 用戶管理相關服務
│   │   │   ├── models/          # 用戶管理相關模型
│   │   │   ├── state/           # NgRx狀態管理
│   │   │   │   ├── actions/     # Actions
│   │   │   │   ├── effects/     # Effects
│   │   │   │   ├── reducers/    # Reducers
│   │   │   │   ├── selectors/   # Selectors
│   │   │   │   └── index.ts     # 導出NgRx狀態管理內容
│   │   │   ├── user.module.ts   # 用戶管理模組
│   │   │   └── user-routing.module.ts # 用戶管理模組路由
│   │   ├── product/             # 商品管理模組
│   │   │   ├── components/      # 商品管理相關組件
│   │   │   ├── services/        # 商品管理相關服務
│   │   │   ├── models/          # 商品管理相關模型
│   │   │   ├── state/           # NgRx狀態管理
│   │   │   │   ├── actions/     # Actions
│   │   │   │   ├── effects/     # Effects
│   │   │   │   ├── reducers/    # Reducers
│   │   │   │   ├── selectors/   # Selectors
│   │   │   │   └── index.ts     # 導出NgRx狀態管理內容
│   │   │   ├── product.module.ts  # 商品管理模組
│   │   │   └── product-routing.module.ts # 商品管理模組路由
│   │   └── ...                  # 其他功能模組
│   ├── app-routing.module.ts
│   ├── app.module.ts
│   ├── app.component.ts
│   └── ...
├── assets/
├── environments/
├── styles/
├── index.html
├── main.ts
└── polyfills.ts

core 目錄

可以用來存放 全域 的:

  • 服務(service)

  • 守衛(guard)

  • 攔截器(interceptor)

  • 模型(model)

  • 配置(config)

  • 工具函數(utils)

等等

shared 目錄

可以用來存放 共用、共享 的:

  • 組件(components)

  • 管道(pipes)

  • 指令(directives)

  • 服務(services)

等等

features 目錄

每個業務模組內部包含一個 state 目錄,

用於存放 NgRx 的狀態管理相關文件

NgRx 狀態管理目錄結構

state 目錄

├── state/
│   ├── actions/     # Actions
│   │   └── *.actions.ts
│   ├── effects/     # Effects
│   │   └── *.effects.ts
│   ├── reducers/    # Reducers
│   │   └── *.reducer.ts
│   ├── selectors/   # Selectors
│   │   └── *.selectors.ts
│   └── index.ts     # 導出 NgRx 狀態管理內容

state 目錄詳細內容

  • actions/: 存放所有的 Actions, 用於描述對狀態進行的操作

    • *.actions.ts: 定義 Actions 類型和 Actions 創建函數
  • effects/: 存放所有的 Effects, 用於處理副作用(如異步操作)

    • *.effects.ts: 定義 Effects 類型和 Effects 處理函數
  • reducers/: 存放所有的 Reducers, 用於處理 Actions 並更新狀態

    • *.reducer.ts: 定義 Reducers 類型和 Reducers 處理函數
  • selectors/: 存放所有的 Selectors, 用於從狀態中選擇數據

    • *.selectors.ts: 定義 Selectors 函數
  • index.ts: 導出 state 目錄中的內容, 以便在模組中導入使用

actions/auth.actions.ts

import { createAction, props } from "@ngrx/store";

export const login = createAction(
  "[Auth] Login",
  props<{ username: string; password: string }>()
);

export const loginSuccess = createAction(
  "[Auth] Login Success",
  props<{ user: any }>()
);

export const loginFailure = createAction(
  "[Auth] Login Failure",
  props<{ error: any }>()
);

effects/auth.effects.ts

import { Injectable } from "@angular/core";
import { Actions, createEffect, ofType } from "@ngrx/effects";
import { AuthService } from "../auth.service";
import { login, loginSuccess, loginFailure } from "./auth.actions";
import { catchError, map, switchMap } from "rxjs/operators";
import { of } from "rxjs";

@Injectable()
export class AuthEffects {
  login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(login),
      switchMap((action) =>
        this.authService.login(action.username, action.password).pipe(
          map((user) => loginSuccess({ user })),
          catchError((error) => of(loginFailure({ error })))
        )
      )
    )
  );

  constructor(private actions$: Actions, private authService: AuthService) {}
}

reducers/auth.reducer.ts

import { createReducer, on } from "@ngrx/store";
import { login, loginSuccess, loginFailure } from "./auth.actions";

export const initialState = {
  user: null,
  error: null,
  loading: false,
};

const _authReducer = createReducer(
  initialState,
  on(login, (state) => ({ ...state, loading: true })),
  on(loginSuccess, (state, { user }) => ({ ...state, user, loading: false })),
  on(loginFailure, (state, { error }) => ({ ...state, error, loading: false }))
);

export function authReducer(state: any, action: any) {
  return _authReducer(state, action);
}

selectors/auth.selectors.ts

import { createFeatureSelector, createSelector } from "@ngrx/store";

export const selectAuthState = createFeatureSelector<any>("auth");

export const selectUser = createSelector(
  selectAuthState,
  (state: any) => state.user
);

export const selectAuthError = createSelector(
  selectAuthState,
  (state: any) => state.error
);

export const selectAuthLoading = createSelector(
  selectAuthState,
  (state: any) => state.loading
);

總結

  • core: 存放全域的服務、守衛、攔截器等

  • shared: 存放共享的組件、管道、指令和服務

  • features: 每個業務模組內新增 state 目錄, 存放 NgRx 的 actions、reducers、effects、selectors 等文件, 用於狀態管理

這樣的架構有助於清晰地分離業務邏輯和狀態管理邏輯,

雖然感覺有點多餘,

但當專案變得大的時候,

制定 SOP 有助於提高團隊的協作效率

參考