第123课:工程化实践——Angular 项目初始化与目录结构
前两课我们分别搭建了 React 和 Vue 3 的企业级项目骨架。本节课将使用 Angular 17+(Standalone 模式),从零构建一个具备工程化约束的 Angular 单页应用骨架。Angular 与其他框架最大的不同在于它由 Angular CLI 统一管理项目的创建、开发、构建和测试,并提供了严格的项目结构约定。我们将覆盖 Angular CLI 项目初始化、TypeScript 严格配置、ESLint + Prettier 规范(Angular 默认使用自定义的 angular.json 管理 lint)、Husky + lint-staged 提交拦截、环境变量与 HttpClient 封装、以及 Angular 特有的目录结构分层设计原则——包括 Standalone Components、Services、Guards、Resolvers 和惰性加载路由。学完本课,你将能够快速初始化一个结构清晰、约束到位、可立即投入团队开发的 Angular 工程模板。
1. 项目初始化:Angular CLI 与 Standalone 模式
1.1 使用 Angular CLI 创建项目
1 | # 全局安装 Angular CLI(如已安装可跳过) |
参数说明:
--style=scss:使用 SCSS 作为样式预处理器。--routing=true:自动生成路由配置(app.routes.ts)。--standalone=true:使用 Standalone 模式(Angular 17+ 默认)。每个组件都是独立的,无需 NgModule 包裹。本课基于 Standalone 模式。--ssr=false:不启用服务端渲染(保持为纯客户端 SPA)。
1.2 初始清理
Angular CLI 生成的模板包含一些演示代码。清理后准备骨架:
1 | # 删除默认生成的演示组件(可选,根据需要) |
生成的 src/app/ 目录结构:
1 | src/app/ |
app.config.ts 是 Standalone 模式的核心配置文件,用于注册路由、HTTP 客户端等全局服务。它取代了传统 NgModule 中的 imports 和 providers。
2. TypeScript 严格配置
Angular CLI 生成的 tsconfig.json 默认已经包含了 strict: true。我们需要进一步收紧某些选项,并配置路径别名。
2.1 更新根 tsconfig.json
1 | { |
Angular 特有的编译器选项:
| 选项 | 含义 |
|---|---|
strictInjectionParameters |
强制依赖注入的构造函数参数进行类型检查。 |
strictInputAccessModifiers |
检查模板中 @Input 属性的访问修饰符(如是否误用了 private)。 |
strictTemplates |
对组件模板进行严格的类型检查(如检测 *ngIf 中的类型错误)。 |
2.2 路径别名配置
在 tsconfig.json 中设置了 paths: { "@/*": ["src/app/*"] }。与 React/Vue 相同,Angular 的构建工具(基于 esbuild 或 Webpack)不会自动读取 tsconfig.json 的 paths。需要在 angular.json 中为构建和开发服务器添加别名配置(仅在使用自定义 Webpack 时支持)——实际上 Angular CLI 在 ng build 和 ng serve 阶段尚不直接支持 TypeScript 路径别名的自动解析。社区有两种解决方案:
- 方案一(推荐):不使用
@/别名,采用 Angular 标准路径。Angular 的模块系统天然支持相对路径和基于src/app/的绝对导入。 - 方案二:如果团队坚持使用
@/别名,可以通过angular.json中的build.options配合第三方工具(如tsconfig-paths-webpack-plugin),但会增加配置复杂度。
本课采用 Angular 标准路径(src/app/ 作为根,不设置路径别名)。这在 Angular 社区是普遍做法,避免了额外的工具依赖。
3. 代码规范:ESLint + Prettier + Husky + lint-staged
Angular CLI 创建的项目默认不包含 ESLint(Angular 10 后已移除 tslint)。需要手动添加。
3.1 安装 ESLint 相关依赖
1 | # Angular ESLint 官方 schematics |
此命令会自动安装 @angular-eslint/builder、@angular-eslint/eslint-plugin、@angular-eslint/template-parser、@typescript-eslint/parser、@typescript-eslint/eslint-plugin、eslint 等依赖,并生成 .eslintrc.json 配置文件。
3.2 ESLint 配置
生成的 .eslintrc.json 包含 Angular 和 TypeScript 的推荐规则。我们需要追加 Prettier 集成和自定义规则:
1 | { |
与 React/Vue 配置的关键差异:
- Angular ESLint 使用
overrides数组分别配置 TypeScript 文件和 HTML 模板文件。 - 使用
@angular-eslint系列插件(而非react-hooks或vue插件)。 - 必须包含
@angular-eslint/template/process-inline-templates以正确解析组件内联模板。
3.3 Prettier 配置
安装 Prettier:
1 | pnpm add -D prettier eslint-config-prettier |
创建 .prettierrc:
1 | { |
创建 .prettierignore:
1 | dist/ |
3.4 Husky + lint-staged 配置
1 | pnpm add -D husky lint-staged |
编辑 .husky/pre-commit:
1 | npx lint-staged |
在 package.json 中添加:
1 | { |
注意:Angular 的 lint 使用 ng lint(调用 angular.json 中配置的 ESLint Builder),而非直接调用 eslint 命令。这确保了 Angular 特有的文件解析和配置集成。
4. 环境变量与 HttpClient 封装
4.1 环境变量配置
Angular 通过 src/environments/ 目录管理环境变量。CLI 生成的项目默认包含两个文件:
1 | // src/environments/environment.ts(开发环境) |
1 | // src/environments/environment.prod.ts(生产环境) |
Angular CLI 在构建时通过 fileReplacements(angular.json 中配置)自动将 environment.ts 替换为 environment.prod.ts。
4.2 HttpClient 封装(拦截器)
Angular 使用 HttpClient 服务进行 HTTP 请求,通过 拦截器(Interceptor) 统一注入 Token、处理错误。
创建 src/app/core/interceptors/auth.interceptor.ts:
1 | import { HttpInterceptorFn } from '@angular/common/http'; |
创建 src/app/core/interceptors/error.interceptor.ts:
1 | import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http'; |
在 src/app/app.config.ts 中注册拦截器:
1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; |
4.3 API 服务示例
创建 src/app/services/user.service.ts:
1 | import { Injectable, inject } from '@angular/core'; |
与 React/Vue 封装的差异:
- Angular 使用
HttpClient内置的params配置自动处理查询字符串,无需手动拼接URLSearchParams。 - Angular 的拦截器(
HttpInterceptorFn)是函数式、可组合的,通过withInterceptors注册。这是 Angular 15+ 推荐的新式写法(之前是类式@Injectable拦截器)。
5. 目录结构分层设计
5.1 推荐目录结构
1 | my-angular-app/ |
5.2 分层原则
- **
core/**:存放仅被AppComponent或根路由使用的单例服务、拦截器、守卫。它们通常是全局性的,与具体业务无关。 - **
shared/**:存放可复用的 UI 组件(ButtonComponent、CardComponent)、自定义管道(truncate)、通用指令。这些组件通过standalone: true和imports声明其依赖,可以被任何特性组件直接导入。 - **
features/**:按业务领域划分的页面组件和服务。例如users/下包含用户列表、用户详情和用户服务。特性内的组件可以自由引用shared/中的组件和core/中的全局服务。 - **
services/**:存放跨特性共享的服务。也可以将服务就近放置在所属的features/目录下(Angular 社区两种风格均可,本课推荐按业务领域就近放置)。 - **
environments/**:环境配置文件,由 Angular CLI 在构建时替换。
5.3 命名约定
| 类型 | 规则 | 示例 |
|---|---|---|
| 组件文件 | kebab-case + .component.ts |
user-list.component.ts |
| 组件选择器 | kebab-case,以 app- 为前缀 |
app-user-list |
| 服务文件 | kebab-case + .service.ts |
user.service.ts |
| 拦截器文件 | kebab-case + .interceptor.ts |
auth.interceptor.ts |
| 守卫文件 | kebab-case + .guard.ts |
auth.guard.ts |
| 管道文件 | kebab-case + .pipe.ts |
truncate.pipe.ts |
| 环境文件 | environment.ts / environment.prod.ts |
6. 路由配置:惰性加载与 Standalone 路由
Angular 17+ 的 Standalone 路由通过 loadComponent 或 loadChildren 实现惰性加载。
6.1 根路由配置
编辑 src/app/app.routes.ts:
1 | import { Routes } from '@angular/router'; |
关键点:
loadComponent替代了传统 NgModule 模式中的loadChildren。它直接返回一个Promise,解析为 Standalone 组件。- Angular CLI 会自动将每个
import()的目标组件拆分为独立的 JavaScript chunk,实现路由级惰性加载。 title属性是 Angular 14+ 引入的,用于设置页面标题(会自动更新document.title)。
6.2 根组件模板
编辑 src/app/app.component.html:
1 | <header> |
6.3 Standalone 组件的最小结构
创建 src/app/features/home/home.component.ts(示例):
1 | import { Component } from '@angular/core'; |
与 React/Vue 组件的差异:
- Angular Standalone 组件必须标记
standalone: true。 - 模板可以是外部文件(
templateUrl)或内联字符串(template),样式同理。 - 选择器(
selector)对应 HTML 标签名。根组件AppComponent的selector通常为app-root,在index.html中对应<app-root></app-root>。特性组件的选择器则在其他组件的模板中使用。
7. 全局样式:SCSS 变量与基础重置
7.1 SCSS 变量
创建 src/styles/_variables.scss:
1 | // 颜色 |
7.2 全局样式
在 src/styles/styles.scss 中(确保 angular.json 的 architect.build.options.styles 中包含此文件):
1 | @use 'variables' as *; |
注意:Angular 使用 @use 语法引入 SCSS 文件(而非 @import,后者在 Sass 中已弃用)。需要确保 angular.json 中 stylePreprocessorOptions.includePaths 包含 src/styles 路径,以便在组件 SCSS 中直接 @use 'variables' 使用全局变量。
在 angular.json 中添加预处理器配置:
1 | "architect": { |
8. 三个框架的项目初始化对比总结
| 维度 | React (Vite) | Vue 3 (Vite) | Angular (CLI) |
|---|---|---|---|
| 脚手架工具 | pnpm create vite |
pnpm create vite |
ng new |
| 类型检查命令 | tsc --noEmit |
vue-tsc --noEmit |
ng build(内置 tsc + Angular 模板检查) |
| ESLint 配置 | eslint:recommended + @typescript-eslint + react-hooks |
同左 + plugin:vue/vue3-recommended + vue-eslint-parser |
@angular-eslint + template overrides |
| 模块系统 | ESM(Vite) | ESM(Vite) | ESM + Angular Modules / Standalone |
| 路由模式 | react-router-dom v6 |
vue-router v4 |
@angular/router(内置) |
| 状态管理 | Zustand / Redux Toolkit | Pinia | Services + RxJS / NgRx |
| HTTP 客户端 | 自定义封装 fetch |
自定义封装 fetch |
HttpClient(内置拦截器) |
| 环境变量 | .env(VITE_ 前缀) |
.env(VITE_ 前缀) |
environments/ 文件夹 + 构建替换 |
| 路径别名 | @/* → src/*(Vite + tsconfig 同步配置) |
同左 | 社区方案(@/* → src/app/*),不推荐 |
课后练习
一、概念自测(选择题 / 填空题)
(单选) Angular CLI 生成的项目中,用于定义应用全局配置(路由、HTTP 拦截器等)的文件是?
A.app.module.ts
B.main.ts
C.app.config.ts
D.angular.json(单选) Angular 17+ 的 Standalone 路由惰性加载使用哪个属性?
A.loadChildren
B.loadComponent
C.component
D.lazyComponent(填空) Angular 中,要统一为所有 HTTP 请求添加
Authorization头,应创建一个______函数,并在provideHttpClient(withInterceptors(...))中注册。(多选) 关于 Angular 项目 TypeScript 配置的描述,哪些是正确的?
A.tsconfig.json的strict: true已包含模板严格检查。
B.angularCompilerOptions.strictTemplates用于对组件模板进行类型检查。
C.strictInjectionParameters强制依赖注入的构造函数参数类型检查。
D. Angular CLI 会自动读取tsconfig.json的paths并配置 Webpack/esbuild 别名。
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要从零创建一个 Angular 17+ Standalone 项目骨架。要求如下:
- 使用
ng new创建项目,SCSS 样式,启用路由,Standalone 模式。 - 配置 TypeScript 严格模式(含
angularCompilerOptions严格检查)。 - 安装并配置 ESLint(
@angular-eslint/schematics)和 Prettier(singleQuote: true,semi: true,trailingComma: 'all'),ESLint extends 包含@angular-eslint/recommended、@typescript-eslint/recommended和prettier。 - 配置 Husky + lint-staged(匹配
*.ts、*.html、*.scss等)。 - 创建
src/environments/环境变量(开发与生产不同的apiBaseUrl)。 - 创建
auth.interceptor.ts和error.interceptor.ts,并在app.config.ts中注册。 - 创建
UserService(使用HttpClient,封装 GET/POST/PATCH/DELETE 方法)。 - 配置惰性加载路由(首页
/、用户列表/users、用户详情/users/:id、404 页面),每个路由使用loadComponent并设置title。 - 创建完整的目录结构(
core/interceptors/、shared/components/ui/、features/、services/)。 - 创建全局 SCSS 变量(
_variables.scss)和基础样式重置。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的完整项目初始化的所有配置文件和核心代码文件。提示词中需明确指定 Angular 特有的配置项(app.config.ts、HttpInterceptorFn、provideHttpClient(withInterceptors(...))、loadComponent、angularCompilerOptions.strictTemplates 等)。
三、Agent 模式下的提示词示例
你是一个资深前端开发 Agent。请创建一个 Angular 17+ Standalone 项目骨架。需要生成/修改以下文件:
- 使用
ng new创建项目(SCSS,路由,Standalone),安装额外依赖:husky、lint-staged、prettier、eslint-config-prettier。使用ng add @angular-eslint/schematics初始化 ESLint。tsconfig.json:strict: true,angularCompilerOptions中strictTemplates: true、strictInjectionParameters: true、strictInputAccessModifiers: true。.eslintrc.json:overrides 中 extends 包含plugin:@angular-eslint/recommended、@typescript-eslint/recommended、prettier。rules 禁止console.log(允许warn/error)。.prettierrc:singleQuote: true,semi: true,trailingComma: 'all',printWidth: 100。.husky/pre-commit:npx lint-staged。package.json:lint-staged配置(*.ts执行eslint --fix+prettier --write,*.html/*.scss/*.md/*.json执行prettier --write)。scripts 包含start、build、lint、format、prepare。src/environments/environment.ts和src/environments/environment.prod.ts:分别设置apiBaseUrl。src/app/core/interceptors/auth.interceptor.ts和src/app/core/interceptors/error.interceptor.ts:HttpInterceptorFn函数,authInterceptor注入 token,errorInterceptor统一错误处理。src/app/app.config.ts:使用provideHttpClient(withInterceptors([authInterceptor, errorInterceptor]))注册拦截器。src/app/services/user.service.ts:@Injectable({ providedIn: 'root' }),使用inject(HttpClient),封装 GET/POST/PATCH/DELETE 方法。src/app/app.routes.ts:使用loadComponent惰性加载首页、用户列表、用户详情、404 页面,设置title。src/app/features/home/home.component.ts、src/app/features/users/user-list/user-list.component.ts、src/app/features/users/user-detail/user-detail.component.ts、src/app/features/not-found/not-found.component.ts:简单的 Standalone 占位组件(standalone: true,内联模板)。src/app/app.component.html:包含<router-outlet>和导航链接。src/styles/_variables.scss和src/styles/styles.scss:全局 SCSS 变量和基础样式重置。angular.json中配置stylePreprocessorOptions.includePaths。- 创建完整目录结构说明(
core/interceptors/、shared/components/ui/、features/、services/)。
所有文件添加 JSDoc 注释,确保可直接运行。完成后列出所有文件内容。
课后练习答案
一、概念自测答案
C
- 解析:Angular 17+ 使用
app.config.ts定义应用全局配置(路由提供者、HTTP 客户端提供者等)。app.module.ts是传统 NgModule 模式的文件,Standalone 模式中不存在。
- 解析:Angular 17+ 使用
B
- 解析:Angular 17+ 的 Standalone 路由使用
loadComponent惰性加载单个组件。loadChildren用于惰性加载子路由配置。
- 解析:Angular 17+ 的 Standalone 路由使用
**
HttpInterceptorFn**(拦截器函数)- 解析:Angular 15+ 的函数式拦截器
HttpInterceptorFn用于统一处理 HTTP 请求和响应,通过withInterceptors注册。
- 解析:Angular 15+ 的函数式拦截器
B、C
- 解析:A 错误,模板严格检查需要额外在
angularCompilerOptions中配置strictTemplates: true,它不是strict: true的一部分。D 错误,Angular CLI 不会自动读取tsconfig.json的paths来配置别名(需要自定义 Webpack 插件或放弃别名)。
- 解析:A 错误,模板严格检查需要额外在
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请从零创建一个 Angular 17+ Standalone 项目骨架。要求:
- 使用
ng new创建项目(SCSS + 路由 + Standalone)。- TypeScript 严格模式(含 angularCompilerOptions 严格检查)。
- ESLint(@angular-eslint)+ Prettier + Husky + lint-staged。
- 环境变量(environments 文件夹)+ HttpClient 拦截器(auth + error)+ UserService。
- 惰性加载路由(首页/用户列表/用户详情/404)+ Standalone 占位组件。
- 全局 SCSS 变量和基础样式重置。
- 完整目录结构(core/interceptors/、shared/components/ui/、features/、services/)。
- 输出所有配置文件、拦截器、服务、路由、组件和样式的完整内容。”