angular-architecture

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

The Scope Rule (REQUIRED)

作用域规则(强制要求)

"Scope determines structure" - Where a component lives depends on its usage.
UsagePlacement
Used by 1 feature
features/[feature]/components/
Used by 2+ features
features/shared/components/
「作用域决定结构」 - 组件的存放位置取决于它的使用范围。
使用范围存放位置
仅被1个功能模块使用
features/[feature]/components/
被2个及以上功能模块使用
features/shared/components/

Example

示例

features/
  shopping-cart/
    shopping-cart.ts          # Main component = feature name
    components/
      cart-item.ts            # Used ONLY by shopping-cart
      cart-summary.ts         # Used ONLY by shopping-cart
  checkout/
    checkout.ts
    components/
      payment-form.ts         # Used ONLY by checkout
  shared/
    components/
      button.ts               # Used by shopping-cart AND checkout
      modal.ts                # Used by multiple features

features/
  shopping-cart/
    shopping-cart.ts          # 主组件 = 功能模块名称
    components/
      cart-item.ts            # 仅被shopping-cart使用
      cart-summary.ts         # 仅被shopping-cart使用
  checkout/
    checkout.ts
    components/
      payment-form.ts         # 仅被checkout使用
  shared/
    components/
      button.ts               # 被shopping-cart和checkout同时使用
      modal.ts                # 被多个功能模块使用

Project Structure

项目结构

src/app/
  features/
    [feature-name]/
      [feature-name].ts       # Main component (same name as folder)
      components/             # Feature-specific components
      services/               # Feature-specific services
      models/                 # Feature-specific types
    shared/                   # ONLY for 2+ feature usage
      components/
      services/
      pipes/
  core/                       # App-wide singletons
    services/
    interceptors/
    guards/
  app.ts
  app.config.ts
  routes.ts
  main.ts

src/app/
  features/
    [feature-name]/
      [feature-name].ts       # 主组件(与文件夹同名)
      components/             # 功能模块专属组件
      services/               # 功能模块专属服务
      models/                 # 功能模块专属类型定义
    shared/                   # 仅用于被2个及以上功能模块使用的资源
      components/
      services/
      pipes/
  core/                       # 应用全局单例资源
    services/
    interceptors/
    guards/
  app.ts
  app.config.ts
  routes.ts
  main.ts

File Naming (REQUIRED)

文件命名规则(强制要求)

No
.component
,
.service
,
.model
suffixes. The folder tells you what it is.
✅ user-profile.ts
❌ user-profile.component.ts

✅ cart.ts
❌ cart.service.ts

✅ user.ts
❌ user.model.ts

不要添加
.component
.service
.model
后缀。文件夹层级已经能说明文件类型。
✅ user-profile.ts
❌ user-profile.component.ts

✅ cart.ts
❌ cart.service.ts

✅ user.ts
❌ user.model.ts

Style Guide

风格指南

What We Follow (from official docs)

我们遵循的规则(来自官方文档)

  • inject()
    over constructor injection
  • class
    and
    style
    bindings over
    ngClass
    /
    ngStyle
  • protected
    for template-only members
  • readonly
    for inputs, outputs, queries
  • Name handlers for action (
    saveUser
    ) not event (
    handleClick
    )
  • Keep lifecycle hooks simple - delegate to well-named methods
  • One concept per file
typescript
@Component({...})
export class UserProfileComponent {
  // 1. Injected dependencies
  private readonly userService = inject(UserService);
  
  // 2. Inputs/Outputs
  readonly userId = input.required<string>();
  readonly userSaved = output<User>();
  
  // 3. Internal state
  private readonly _loading = signal(false);
  readonly loading = this._loading.asReadonly();
  
  // 4. Computed
  protected readonly displayName = computed(() => ...);
  
  // 5. Methods
  save(): void { ... }
}
  • 使用
    inject()
    而非构造函数注入
  • 使用
    class
    style
    绑定而非
    ngClass
    /
    ngStyle
  • 模板仅用成员使用
    protected
    修饰符
  • 输入、输出、查询属性使用
    readonly
    修饰符
  • 事件处理方法以动作命名(如
    saveUser
    )而非事件(如
    handleClick
  • 保持生命周期钩子逻辑简洁 - 委托给命名清晰的方法
  • 每个文件仅对应一个概念
typescript
@Component({...})
export class UserProfileComponent {
  // 1. 注入的依赖
  private readonly userService = inject(UserService);
  
  // 2. 输入/输出属性
  readonly userId = input.required<string>();
  readonly userSaved = output<User>();
  
  // 3. 内部状态
  private readonly _loading = signal(false);
  readonly loading = this._loading.asReadonly();
  
  // 4. 计算属性
  protected readonly displayName = computed(() => ...);
  
  // 5. 方法
  save(): void { ... }
}

What We Override

我们修改的规则

Official SaysWe DoWhy
user-profile.component.ts
user-profile.ts
Redundant - folder tells context
user.service.ts
user.ts
Same

官方要求我们的做法原因
user-profile.component.ts
user-profile.ts
冗余 - 文件夹已说明上下文
user.service.ts
user.ts
同理

Commands

命令

bash
undefined
bash
undefined

New project

新建项目

ng new my-app --style=scss --ssr=false
ng new my-app --style=scss --ssr=false

Component in feature

在功能模块中创建组件

ng g c features/products/components/product-card --flat
ng g c features/products/components/product-card --flat

Service in feature

在功能模块中创建服务

ng g s features/products/services/product --flat
ng g s features/products/services/product --flat

Guard in core

在core中创建守卫

ng g g core/guards/auth --functional

---
ng g g core/guards/auth --functional

---

Resources

参考资源