本文档定义了 Angular 项目开发过程中必须遵守的规范,以确保代码质量、一致性和可维护性。
强制要求:所有组件必须使用 ng g c 命令生成,禁止手动创建单文件组件。
# ✅ 正确:使用 Angular CLI 生成完整组件
ng g c features/users/user-list
# ❌ 错误:手动创建单文件组件
# 禁止创建类似以下的单文件组件:
@Component({
selector: 'app-user-list',
template: `<div>用户列表</div>`,
styles: [`div { color: red; }`]
})
export class UserListComponent {}
生成后的组件结构:
src/app/features/user-list/
├── user-list.component.ts # 组件类
├── user-list.component.html # 模板
├── user-list.component.scss # 样式
└── user-list.component.spec.ts # 单元测试
强制要求:默认使用 Standalone 组件,不使用 NgModule。
// ✅ 正确:Standalone 组件
@Component({
selector: 'app-user-card',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
templateUrl: './user-card.component.html',
styleUrls: ['./user-card.component.scss']
})
export class UserCardComponent {}
// ❌ 错误:使用 NgModule 的组件(禁止新建)
@Component({
selector: 'app-user-card',
moduleId: module.id,
templateUrl: './user-card.component.html'
})
export class UserCardComponent {}
一个组件只负责一个功能,避免"万能组件"。
// ✅ 好:职责单一
UserListComponent // 只负责用户列表显示
UserCardComponent // 只负责用户卡片显示
UserFormComponent // 只负责用户表单
// ❌ 差:职责过多
UserManagementComponent // 同时负责列表、详情、编辑、删除
@Component({
selector: 'app-button',
template: `
<button
[disabled]="disabled"
[type]="type"
(click)="handleClick()">
{{ label }}
</button>
`
})
export class ButtonComponent {
@Input() label = '按钮';
@Input() disabled = false;
@Input() type: 'button' | 'submit' = 'button';
@Output() clicked = new EventEmitter<void>();
handleClick() {
this.clicked.emit();
}
}
使用 Angular 绑定机制,禁止使用 document.querySelector 等直接 DOM 操作。
// ✅ 正确:使用模板绑定
template: `<div [class.active]="isActive">内容</div>`
// ❌ 错误:直接操作 DOM
ngAfterViewInit() {
document.querySelector('.active').classList.add('highlight');
}
强制要求:所有路由必须写在 app.routes.ts 中,禁止在 feature 目录下创建独立的 .routes.ts 文件。
// ✅ 正确:所有路由集中在 app.routes.ts
// src/app/app.routes.ts
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./layouts/main-layout/main-layout.component')
.then(m => m.MainLayoutComponent),
children: [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{
path: 'dashboard',
loadComponent: () => import('./features/dashboard/dashboard.component')
.then(m => m.DashboardComponent)
},
{
path: 'users',
loadComponent: () => import('./features/users/user-list/user-list.component')
.then(m => m.UserListComponent)
},
{
path: 'users/:id',
loadComponent: () => import('./features/users/user-detail/user-detail.component')
.then(m => m.UserDetailComponent)
}
]
},
{
path: 'login',
loadComponent: () => import('./features/auth/login/login.component')
.then(m => m.LoginComponent)
},
{
path: '**',
loadComponent: () => import('./features/not-found/not-found.component')
.then(m => m.NotFoundComponent)
}
];
❌ 错误:禁止在 feature 目录下创建独立的路由文件
// ❌ 错误:禁止创建 features/users/users.routes.ts
// features/users/users.routes.ts
export const USER_ROUTES: Routes = [
{ path: '', component: UserListComponent },
{ path: ':id', component: UserDetailComponent }
];
// ❌ 错误:禁止在 app.routes.ts 中引用外部路由文件
export const routes: Routes = [
{
path: 'users',
loadChildren: () => import('./features/users/users.routes')
.then(m => m.USER_ROUTES) // ❌ 禁止
}
];
强制要求:所有功能组件必须使用懒加载。
// ✅ 正确:懒加载组件
{
path: 'users',
loadComponent: () => import('./features/users/user-list/user-list.component')
.then(m => m.UserListComponent)
}
// ❌ 错误:直接导入(立即加载)
import { UserListComponent } from './features/users/user-list/user-list.component';
export const routes: Routes = [
{ path: 'users', component: UserListComponent } // ❌
];
敏感页面必须使用路由守卫。
// core/guards/auth.guard.ts
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
router.navigate(['/login'], {
queryParams: { returnUrl: state.url }
});
return false;
};
// app.routes.ts 中使用
{
path: 'admin',
loadComponent: () => import('./features/admin/admin.component')
.then(m => m.AdminComponent),
canActivate: [authGuard, adminGuard]
}
使用 kebab-case 命名路由路径。
// ✅ 正确
{ path: 'user-profile', component: UserProfileComponent }
{ path: 'order-management', component: OrderManagementComponent }
// ❌ 错误
{ path: 'userProfile', component: UserProfileComponent }
{ path: 'OrderManagement', component: OrderManagementComponent }
必须配置 404 页面。
export const routes: Routes = [
// ... 其他路由
{
path: '**',
loadComponent: () => import('./features/not-found/not-found.component')
.then(m => m.NotFoundComponent)
}
];
嵌套路由也必须写在 app.routes.ts 中。
export const routes: Routes = [
{
path: 'products',
loadComponent: () => import('./features/products/product-list/product-list.component')
.then(m => m.ProductListComponent)
},
{
path: 'products/:id',
loadComponent: () => import('./features/products/product-detail/product-detail.component')
.then(m => m.ProductDetailComponent)
},
{
path: 'products/:id/edit',
loadComponent: () => import('./features/products/product-edit/product-edit.component')
.then(m => m.ProductEditComponent)
}
];
优先使用 inject() 函数,替代构造函数注入。
// ✅ 正确:使用 inject()
@Injectable({
providedIn: 'root'
})
export class UserService {
private http = inject(HttpClient);
private logger = inject(LoggerService);
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
}
// ❌ 错误:构造函数注入(仅在必须时使用)
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(
private http: HttpClient,
private logger: LoggerService
) {}
}
根据需要选择正确的作用域。
// 全局服务(大多数情况)
@Injectable({
providedIn: 'root'
})
export class UserService {}
// 组件级服务(需要多个实例时)
@Component({
selector: 'app-cart',
providers: [CartService] // 每个组件实例都有独立的服务实例
})
export class CartComponent {}
所有 HTTP 请求方法必须返回 Observable。
@Injectable({
providedIn: 'root'
})
export class UserService {
private http = inject(HttpClient);
// ✅ 正确
getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users');
}
// ❌ 错误:返回 Promise
async getUsers(): Promise<User[]> {
return fetch('/api/users').then(res => res.json());
}
}
使用拦截器统一处理认证、错误等。
// core/interceptors/auth.interceptor.ts
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = inject(TokenService).getToken();
if (token) {
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next.handle(authReq);
}
return next.handle(req);
}
}
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([authInterceptor, errorInterceptor])
)
]
};
安装新包后或移除功能后,必须清除未使用的依赖。
# ✅ 正确添加依赖
pnpm add package-name
# ✅ 正确移除依赖(同时从 package.json 和 pnpm-lock.yaml 中移除)
pnpm remove package-name
# ✅ 消除重复依赖(可选)
pnpm dedupe
# ❌ 错误:直接删除 node_modules 中的包
注意:使用 pnpm remove 是清除依赖的唯一正确方式,它会同时更新 package.json 和锁文件。
{
"dependencies": {
"@angular/core": "^20.0.0",
"@angular/common/http": "^20.0.0"
},
"devDependencies": {
"@angular/cli": "^20.0.0",
"prettier": "^3.0.0",
"typescript": "~5.6.0"
}
}
以下类型的包禁止使用:
| 禁止类型 | 示例 | 原因 |
|---|---|---|
| 已弃用的包 | @types/node@12 |
安全风险 |
| jQuery 及插件 | jquery, bootstrap |
与 Angular 理念冲突 |
| 原生 JS 库 | 原生 XMLHttpRequest | 应使用 HttpClient |
生产环境必须锁定依赖版本。
// ✅ 正确:锁定主版本
"dependencies": {
"@angular/core": "^20.0.0"
}
// ❌ 错误:使用浮动版本
"dependencies": {
"@angular/core": "20"
}
src/app/
├── core/ # 核心服务(全局单例)
│ ├── interceptors/ # HTTP 拦截器
│ ├── guards/ # 路由守卫
│ ├── services/ # 核心服务
│ └── models/ # 全局数据模型
│
├── shared/ # 共享资源(Standalone)
│ ├── components/ # 共享组件
│ ├── directives/ # 共享指令
│ └── pipes/ # 共享管道
│
├── features/ # 功能模块(懒加载)
│ ├── dashboard/
│ │ ├── dashboard.component.ts
│ │ ├── dashboard.component.html
│ │ ├── dashboard.component.scss
│ │ └── components/ # 功能内部子组件
│ ├── users/
│ └── products/
│
├── layouts/ # 布局组件
│ ├── main-layout/
│ └── auth-layout/
│
├── environments/ # 环境配置
│ ├── environment.ts
│ └── environment.prod.ts
│
├── app.component.ts
├── app.config.ts
└── app.routes.ts
Core 服务使用 providedIn: 'root',在 app.config.ts 中统一配置拦截器。
// core/services/auth.service.ts
@Injectable({
providedIn: 'root'
})
export class AuthService {
private http = inject(HttpClient);
private tokenService = inject(TokenService);
isAuthenticated(): boolean {
return !!this.tokenService.getToken();
}
login(credentials: any): Observable<any> {
return this.http.post('/api/auth/login', credentials);
}
}
// core/interceptors/auth.interceptor.ts
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = inject(TokenService).getToken();
if (token) {
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next.handle(authReq);
}
return next.handle(req);
}
}
// app.config.ts - 统一注册拦截器
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(
withInterceptors([authInterceptor, errorInterceptor])
)
]
};
注意:在 Standalone 模式下,不再使用 NgModule,统一使用 providers 数组注册服务。
在 Standalone 模式下,共享组件、指令、管道直接在组件中导入,不再使用 SharedModule。
// 在组件中按需导入
@Component({
selector: 'app-user-list',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
DateFormatPipe, // 只导入需要的
PermissionDirective // 只导入需要的
]
})
export class UserListComponent {}
目录结构中的 shared 目录:
src/app/
├── shared/
│ ├── components/ # 共享组件(直接导入)
│ │ ├── button/
│ │ ├── modal/
│ │ └── data-table/
│ ├── directives/ # 共享指令
│ │ └── permission.directive.ts
│ └── pipes/ # 共享管道
│ └── date-format.pipe.ts
注意:不再需要 shared.module.ts,所有共享资源都是 Standalone 组件/指令/管道。
每个功能模块独立,使用懒加载,路由集中在 app.routes.ts。
// app.routes.ts
export const routes: Routes = [
{
path: 'users',
loadComponent: () => import('./features/users/user-list/user-list.component')
.then(m => m.UserListComponent)
},
{
path: 'users/:id',
loadComponent: () => import('./features/users/user-detail/user-detail.component')
.then(m => m.UserDetailComponent)
}
];
禁止创建独立的路由文件:
// ❌ 禁止:features/users/users.routes.ts
// ❌ 禁止:features/products/products.routes.ts
| 类型 | 规范 | 示例 |
|---|---|---|
| 组件 | kebab-case.component.ts |
user-profile.component.ts |
| 服务 | kebab-case.service.ts |
auth.service.ts |
| 指令 | kebab-case.directive.ts |
permission.directive.ts |
| 管道 | kebab-case.pipe.ts |
date-format.pipe.ts |
| 守卫 | kebab-case.guard.ts |
auth.guard.ts |
| 拦截器 | kebab-case.interceptor.ts |
auth.interceptor.ts |
| 模型 | kebab-case.model.ts |
user.model.ts |
| 组件目录 | kebab-case/ |
user-profile/ |
| 类型 | 规范 | 示例 |
|---|---|---|
| 组件类 | PascalCase + Component | UserProfileComponent |
| 服务类 | PascalCase + Service | AuthService |
| 指令类 | PascalCase + Directive | PermissionDirective |
| 管道类 | PascalCase + Pipe | DateFormatPipe |
| 守卫类 | PascalCase + Guard | AuthGuard |
| 模型接口 | PascalCase | User, UserProfile |
| 拦截器类 | PascalCase + Interceptor | AuthInterceptor |
// 变量:camelCase
const userName = '张三';
const isLoading = false;
const userList: User[] = [];
// 函数:camelCase
function getUserById(id: number): User { }
function handleClick(): void { }
// 常量:UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = '/api';
使用 kebab-case,多层嵌套用斜杠分隔。
// ✅ 正确
'/user-profile'
'/order-management/list'
'/admin/system-settings'
// ❌ 错误
'/userProfile'
'/orderManagement'
'/admin/systemSettings'
1. 禁止使用 any,必须指定类型。
// ✅ 正确
const user: User = { id: 1, name: '张三' };
function getUser(): Observable<User> { }
// ❌ 错误
const user: any = { id: 1, name: '张三' };
function getUser(): any { }
2. 使用 strict 模式。
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
3. 接口和类型别名优先使用接口。
// ✅ 优先使用接口
interface User {
id: number;
name: string;
}
// 仅在需要联合类型或元组时使用类型别名
type UserRole = 'admin' | 'user' | 'guest';
type Point = [number, number];
4. 使用可选链和空值合并。
// ✅ 正确
const name = user?.profile?.name ?? '匿名';
const users = data ?? [];
// ❌ 错误
const name = user && user.profile && user.profile.name || '匿名';
1. 组件中及时取消订阅。
import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({...})
export class UserListComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
this.userService.getUsers()
.pipe(takeUntil(this.destroy$))
.subscribe(users => this.users = users);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
注意:必须导入 takeUntil 操作符和 Subject。
2. 优先使用 async pipe。
@Component({
selector: 'app-user-list',
template: `
@for (user of users$ | async; track user.id) {
<div>{{ user.name }}</div>
}
`
})
export class UserListComponent {
users$ = this.userService.getUsers();
}
3. 统一错误处理。
// 使用 catchError 操作符
this.http.get<User[]>('/api/users').pipe(
catchError(error => {
console.error('请求失败', error);
return of([]);
})
);
Angular 20 推荐使用 Signals 替代部分 RxJS 使用场景。
1. 创建和使用 Signals。
import { signal, computed, effect } from '@angular/core';
// 创建可写的 signal
const count = signal(0);
const user = signal<User | null>(null);
// 读取值(调用函数)
console.log(count()); // 0
// 更新值
count.set(10);
count.update(value => value + 1);
// 创建计算属性
const doubled = computed(() => count() * 2);
// 副作用
effect(() => {
console.log(`Count changed: ${count()}`);
});
2. 在服务中使用 Signals 替代 BehaviorSubject。
// ✅ 推荐:使用 Signals
@Injectable({
providedIn: 'root'
})
export class CartService {
private items = signal<CartItem[]>([]);
totalItems = computed(() => this.items().length);
totalPrice = computed(() =>
this.items().reduce((sum, item) => sum + item.price, 0)
);
addItem(item: CartItem) {
this.items.update(current => [...current, item]);
}
removeItem(id: number) {
this.items.update(current => current.filter(i => i.id !== id));
}
}
// ❌ 不推荐:使用 BehaviorSubject
@Injectable({
providedIn: 'root'
})
export class CartService {
private items$ = new BehaviorSubject<CartItem[]>([]);
totalItems$ = this.items$.pipe(map(items => items.length));
}
3. 在组件中使用 Signals。
@Component({
selector: 'app-cart',
template: `
<p>商品数量:{{ cart.totalItems() }}</p>
<p>总价:{{ cart.totalPrice() | currency:'¥' }}</p>
`
})
export class CartComponent {
cart = inject(CartService);
}
4. Signals vs RxJS 选择指南。
| 场景 | 推荐 | 原因 |
|---|---|---|
| 组件内本地状态 | Signals | 简单、直接 |
| 服务中的共享状态 | Signals | 性能更好 |
| HTTP 请求 | RxJS | 强大的操作符支持 |
| 复杂异步流程 | RxJS | 可取消、retry、重试 |
| 实时数据流 | RxJS | WebSocket 等 |
仅在必要时添加注释,避免无意义注释。
// ✅ 好的注释:解释为什么
// 使用 setTimeout 而非 setInterval,避免内存泄漏
setTimeout(() => this.refresh(), 5000);
// ✅ 好的注释:复杂逻辑说明
/**
* 计算用户积分
* 规则:每消费1元积1分,VIP用户双倍积分
*/
calculatePoints(amount: number, isVip: boolean): number {
const basePoints = amount;
return isVip ? basePoints * 2 : basePoints;
}
// ❌ 坏的注释:显而易见的说明
// 设置用户名
this.userName = '张三';
强制使用 SCSS 预处理器。
ng new my-app --style=scss
全局样式使用 src/styles.scss,直接定义变量和混入。
// src/styles.scss
// 全局变量
$primary-color: #1976d2;
$border-radius: 4px;
// 混入
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
// CSS 重置
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
// 全局样式
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.5;
color: #333;
}
// 组件样式使用 BEM
.user-card {
padding: 16px;
&__header {
@include flex-center;
}
&__avatar {
width: 48px;
border-radius: 50%;
}
&--featured {
border: 2px solid gold;
}
}
angular.json 配置(自动生成,无需修改):
{
"styles": ["src/styles.scss"],
"stylePreprocessorOptions": {
"includePaths": ["src"]
}
}
注意:在组件 SCSS 中可以直接使用全局变量和混入。
1. 使用 :host 选择器。
:host {
display: block;
}
:host(.featured) {
border: 2px solid gold;
}
2. 禁止使用深度选择器。
// ✅ 正确:使用固定类名
.parent { }
.parent__child { }
// ❌ 错误:/deep/ 已废弃
.parent /deep/ .child { }
// ❌ 错误:::ng-deep 已废弃,不应使用
.parent ::ng-deep .child { }
深度选择器会破坏组件封装,应使用固定的类名或通过 @Input 传递样式配置。
3. 响应式样式使用变量。
// 在 styles.scss 或组件中定义断点
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
.container {
width: 100%;
@media (min-width: $breakpoint-md) {
width: 720px;
}
}
使用 Angular 绑定机制,禁止使用 innerHTML 绑定未处理的内容。
// ✅ 正确:自动转义
template: `<div>{{ userContent }}</div>`
// ❌ 错误:可能导致 XSS
template: `<div [innerHTML]="userContent"></div>`
// 如果必须使用 innerHTML,必须先净化
import { DomSanitizer } from '@angular/platform-browser';
constructor(private sanitizer: DomSanitizer) {}
getSafeHtml(html: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(html);
}
使用 provideHttpClient 的 withXsrfConfiguration 配置 CSRF 防护。
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
})
)
]
};
禁止在代码中硬编码敏感信息。
// ❌ 错误
const API_KEY = 'sk-xxxxx-xxxxx';
const DATABASE_URL = 'mongodb://admin:password@localhost:27017';
// ✅ 正确:使用环境变量
// environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api'
};
// environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.example.com'
};
敏感路由必须添加守卫。
// 需要认证的路由
{ path: 'profile', canActivate: [authGuard] }
// 需要特定角色的路由
{ path: 'admin', canActivate: [authGuard, adminGuard] }
所有功能组件必须使用懒加载,使用 loadComponent。
// ✅ 正确
{
path: 'users',
loadComponent: () => import('./features/users/user-list/user-list.component')
.then(m => m.UserListComponent)
}
// ❌ 错误:禁止使用 loadChildren
{
path: 'users',
loadChildren: () => import('./features/users/users.routes')
.then(m => m.USER_ROUTES) // ❌ 禁止
}
// ❌ 错误:直接导入
import { UserListComponent } from './features/users/user-list/user-list.component';
{ path: 'users', component: UserListComponent } // ❌ 禁止
高频更新的组件使用 OnPush。
@Component({
selector: 'app-user-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `...`
})
export class UserListComponent {}
@for 循环必须使用 track。
<!-- ✅ 正确 -->
@for (user of users; track user.id) {
<div>{{ user.name }}</div>
}
<!-- ❌ 错误:缺少 track -->
@for (user of users) {
<div>{{ user.name }}</div>
}
使用 loading="lazy" 延迟加载图片。
<img src="avatar.jpg" alt="头像" loading="lazy" />
1. RxJS 订阅必须取消。
@Component({...})
export class UserListComponent implements OnDestroy {
private destroy$ = new Subject<void>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
2. 推荐使用 Signals 替代 RxJS 管理组件状态,可以避免订阅管理问题。
// ✅ 推荐:使用 Signals,无需手动取消订阅
@Injectable({
providedIn: 'root'
})
export class CartService {
private items = signal<CartItem[]>([]);
totalItems = computed(() => this.items().length);
}
// 组件中使用
@Component({
template: `<p>{{ cart.totalItems() }}</p>`
})
export class CartComponent {
cart = inject(CartService);
}
// Signals 会自动管理依赖,无需担心内存泄漏
定期分析打包大小。
# 生成打包分析
ng build --stats-json
npx webpack-bundle-analyzer dist/*/stats.json
<type>(<scope>): <subject>
<body>
<footer>
| 类型 | 说明 |
|---|---|
| feat | 新功能 |
| fix | 修复 bug |
| docs | 文档更新 |
| style | 代码格式(不影响功能) |
| refactor | 重构 |
| perf | 性能优化 |
| test | 测试相关 |
| build | 构建相关 |
| ci | CI 相关 |
| chore | 其他更改 |
# 功能提交
git commit -m "feat(user): 添加用户列表分页功能"
# 修复提交
git commit -m "fix(auth): 修复登录失败后 token 未清除的问题"
# 重构提交
git commit -m "refactor(service): 重构 UserService 使用 inject()"
# 提交前检查
git commit -m "feat(cart): 添加购物车功能
- 添加购物车服务
- 添加商品加减功能
- 添加结算页面"
Closes #123
禁止提交以下内容:
# node_modules
node_modules/
# 构建产物
dist/
build/
# IDE 配置
.idea/
.vscode/
# 环境配置文件
.env
*.local
# npm 锁文件(禁止提交 npm 的 lock 文件)
package-lock.json
# 日志文件
*.log
# 操作系统文件
.DS_Store
Thumbs.db
pnpm-lock.yaml 必须提交,它是 pnpm 的锁文件,确保团队成员安装的依赖版本一致。
ng g c 生成loadComponentany 类型如果需要代码格式化,可以配置 Prettier。
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "auto"
}
安装和使用:
pnpm add -D prettier
pnpm exec prettier --write src/
文档版本:2.0.0 | 对应 Angular 20.x | 更新日期:2025