angular-architecture by gentleman-programming/gentleman-skills
npx skills add https://github.com/gentleman-programming/gentleman-skills --skill angular-architecture“作用域决定结构” —— 组件的存放位置取决于其使用范围。
| 使用范围 | 存放位置 |
|---|---|
| 仅被 1 个功能使用 | features/[功能名]/components/ |
| 被 2 个及以上功能使用 | features/shared/components/ |
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 # 被多个功能使用
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
src/app/
features/
[功能名称]/
[功能名称].ts # 主组件(与文件夹同名)
components/ # 功能特定的组件
services/ # 功能特定的服务
models/ # 功能特定的类型
shared/ # 仅用于 2 个及以上功能
components/
services/
pipes/
core/ # 应用范围内的单例
services/
interceptors/
guards/
app.ts
app.config.ts
routes.ts
main.ts
不使用 .component、.service、.model 等后缀。文件夹本身已说明其内容。
✅ user-profile.ts
❌ user-profile.component.ts
✅ cart.ts
❌ cart.service.ts
✅ user.ts
❌ user.model.ts
优先使用 inject() 而非构造函数注入
优先使用 class 和 style 绑定而非 ngClass/ngStyle
模板专用成员使用 protected
输入、输出、查询使用 readonly
为操作命名处理器(如 saveUser),而非事件(如 handleClick)
保持生命周期钩子简单——委托给命名良好的方法
每个文件只包含一个概念
@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 { ... } }
| 官方建议 | 我们的做法 | 原因 |
|---|---|---|
user-profile.component.ts | user-profile.ts | 冗余——文件夹已说明上下文 |
user.service.ts | user.ts | 同上 |
# 新建项目
ng new my-app --style=scss --ssr=false
# 在功能中创建组件
ng g c features/products/components/product-card --flat
# 在功能中创建服务
ng g s features/products/services/product --flat
# 在 core 中创建守卫
ng g g core/guards/auth --functional
每周安装量
129
代码仓库
GitHub 星标数
354
首次出现
2026 年 1 月 24 日
安全审计
安装于
opencode111
gemini-cli102
codex102
github-copilot101
kimi-cli94
amp92
"Scope determines structure" - Where a component lives depends on its usage.
| Usage | Placement |
|---|---|
| Used by 1 feature | features/[feature]/components/ |
| Used by 2+ features | features/shared/components/ |
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
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
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
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
| Official Says | We Do | Why |
|---|---|---|
user-profile.component.ts | user-profile.ts | Redundant - folder tells context |
user.service.ts | user.ts | Same |
# New project
ng new my-app --style=scss --ssr=false
# Component in feature
ng g c features/products/components/product-card --flat
# Service in feature
ng g s features/products/services/product --flat
# Guard in core
ng g g core/guards/auth --functional
Weekly Installs
129
Repository
GitHub Stars
354
First Seen
Jan 24, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode111
gemini-cli102
codex102
github-copilot101
kimi-cli94
amp92
Next.js 15+ 最佳实践指南:文件约定、RSC边界、异步模式与性能优化
1,000 周安装
One concept per file
@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 { ... } }