angular-frontend-clean-architecture
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFrontend Angular – Clean Architecture + Standalone
Angular前端 – 整洁架构 + 独立组件
Guia para criar e manter frontends Angular com estrutura por features (vertical slice), componentes standalone, signals e ng-zorro-antd.
本指南用于创建和维护采用按功能划分(垂直切片)结构、独立组件、signals和ng-zorro-antd的Angular前端。
Quando usar esta skill
何时使用本方案
- Adicionar nova página/feature (nova pasta em com listagem, formulário, service, modelos).
pages/ - Criar componente reutilizável em , diretiva em
shared/components/, pipe emshared/directives/.shared/pipes/ - Adicionar service (HTTP, estado global), guard, interceptor.
- Definir ou alterar rotas em .
app.routes.ts - Revisar ou refatorar código para seguir a estrutura e convenções do boilerplate.
- 添加新的页面/功能模块(在目录下新建文件夹,包含列表、表单、服务、模型)。
pages/ - 在中创建可复用组件,在
shared/components/中创建指令,在shared/directives/中创建管道。shared/pipes/ - 添加服务(HTTP请求、全局状态)、路由守卫、拦截器。
- 在中定义或修改路由。
app.routes.ts - 审查或重构代码,使其符合该模板的结构与规范。
Estrutura do projeto
项目结构
frontend/src/app/
├── app.ts # Bootstrap, providers (HTTP, interceptors)
├── app.config.ts # provideRouter, provideHttpClient, etc.
├── app.routes.ts # Rotas (guards quando necessário)
├── layout/ # Layout principal (header, drawer, outlet)
│ └── main-layout/
├── pages/ # Features por domínio (vertical slice)
│ └── products/
│ ├── product.model.ts
│ ├── product.service.ts
│ └── products-list/ # Uma tela = uma pasta com tudo junto
│ ├── products-list.component.ts
│ ├── products-list.component.html
│ ├── products-list.component.scss
│ └── products-list.component.spec.ts
├── shared/
│ ├── components/ # Componentes reutilizáveis (page-header, loading, etc.)
│ ├── directives/ # Diretivas (autofocus, currency-mask, etc.)
│ ├── pipes/
│ ├── services/ # Serviços globais (loading, layout)
│ ├── guards/
│ ├── interceptors/
│ └── utils/
└── environments/ # environment.apiBaseUrl, etc.Regra: pages/ contém uma pasta por domínio (ex.: products). Dentro da feature ficam o modelo, o service e uma pasta por tela (cada tela com component, html, scss e spec juntos). Nem toda feature tem listagem ou formulário — cria-se só as telas que existirem. shared/ contém apenas o que é reutilizado entre páginas.
frontend/src/app/
├── app.ts # 启动文件、提供者配置(HTTP、拦截器)
├── app.config.ts # 路由提供者、HttpClient提供者等配置
├── app.routes.ts # 路由配置(必要时添加守卫)
├── layout/ # 主布局(头部、侧边栏、路由出口)
│ └── main-layout/
├── pages/ # 按业务域划分的功能模块(垂直切片)
│ └── products/
│ ├── product.model.ts
│ ├── product.service.ts
│ └── products-list/ # 一个页面对应一个文件夹,包含所有相关文件
│ ├── products-list.component.ts
│ ├── products-list.component.html
│ ├── products-list.component.scss
│ └── products-list.component.spec.ts
├── shared/
│ ├── components/ # 可复用组件(页面头部、加载动画等)
│ ├── directives/ # 指令(自动聚焦、货币掩码等)
│ ├── pipes/
│ ├── services/ # 全局服务(加载状态、布局控制)
│ ├── guards/
│ ├── interceptors/
│ └── utils/
└── environments/ # 环境配置(如environment.apiBaseUrl)规则:pages/下每个业务域对应一个文件夹(例如:products)。功能模块内包含模型、服务,以及每个页面对应的文件夹(每个页面的组件、html、scss和spec文件放在同一目录)。并非所有功能模块都需要列表或表单,仅创建实际存在的页面。**shared/**仅存放可在多个页面间复用的内容。
Checklist – Nova feature (ex.: Orders)
新功能模块 Checklist(示例:Orders)
Use este checklist e crie os itens na ordem indicada.
Modelo e service
- : interfaces (ex.:
pages/orders/order.model.ts,Order).OrderCreateRequest - :
pages/orders/order.service.ts,@Injectable({ providedIn: 'root' }), métodos que retornaminject(HttpClient)(list, getById, create, update, delete). UsarObservable<T>para base da API.environment.apiBaseUrl
Telas da feature (cada uma em sua pasta, tudo junto)
- Uma pasta por tela em (ex.:
pages/orders/), comorders-list/,*.component.ts,*.component.html,*.component.scssna mesma pasta.*.component.spec.ts - Componente: ,
ChangeDetectionStrategy.OnPush,host: { class: 'app-orders-list' }, signals/computed conforme a tela. Template:inject(OrderService)/@if,@forou[class.x],[style.x]notrack item.id.@for - Se a tela for formulário: form reativo, , validações; helper opcional (validação/build request) na mesma pasta; diretivas compartilhadas (ex.:
FormBuilder) quando aplicável.appCurrencyMask
Rotas
- Em : uma rota por tela que existir na feature, com
app.routes.tsquando necessário.canActivate: [authGuard]
Shared (somente se reutilizável)
- Componente/diretiva/pipe em apenas quando usado em mais de uma feature ou quando for UI genérica (ex.: page-header, loading).
shared/
按照以下顺序完成各项任务:
模型与服务
- :定义接口(例如:
pages/orders/order.model.ts、Order)。OrderCreateRequest - :添加
pages/orders/order.service.ts,通过@Injectable({ providedIn: 'root' })注入HttpClient,编写返回inject(HttpClient)的方法(列表查询、详情查询、创建、更新、删除)。使用Observable<T>作为API基础地址。environment.apiBaseUrl
功能模块页面(每个页面对应一个文件夹,所有文件放在一起)
- 在下为每个页面创建文件夹(例如:
pages/orders/),包含orders-list/、*.component.ts、*.component.html、*.component.scss。*.component.spec.ts - 组件配置:设置,添加
ChangeDetectionStrategy.OnPush,通过host: { class: 'app-orders-list' }注入服务,根据页面需求使用signals/computed。模板中使用inject(OrderService)/@if,通过@for或[class.x]绑定样式,[style.x]循环中添加@for。track item.id - 如果是表单页面:使用响应式表单、和验证规则;可在同一文件夹中添加可选的辅助工具(验证/请求构建);适用时使用共享指令(例如:
FormBuilder)。appCurrencyMask
路由配置
- 在中:为功能模块的每个页面添加路由,必要时配置
app.routes.ts。canActivate: [authGuard]
共享资源(仅用于可复用内容)
- 仅当内容可在多个功能模块复用或属于通用UI(例如:页面头部、加载动画)时,才在下创建组件/指令/管道。
shared/
Padrões de código
代码规范
Componente standalone (sem – é padrão no Angular 21)
standalone: truetypescript
import { ChangeDetectionStrategy, Component, inject, input, signal } from '@angular/core';
@Component({
selector: 'app-page-header',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'app-page-header' },
imports: [RouterLink, NzBreadCrumbModule],
templateUrl: './page-header.component.html',
styleUrl: './page-header.component.scss'
})
export class PageHeaderComponent {
title = input.required<string>();
subtitle = input<string>();
breadcrumb = input<BreadcrumbItem[]>([]);
}Service com HttpClient
typescript
@Injectable({ providedIn: 'root' })
export class ProductService {
private readonly http = inject(HttpClient);
private readonly baseUrl = `${environment.apiBaseUrl}/api/products`;
list(): Observable<Product[]> {
return this.http.get<Product[]>(this.baseUrl);
}
}Template – controle de fluxo e bindings
html
@if (loading()) {
<nz-spin nzSimple></nz-spin>
} @else {
@for (item of items(); track item.id) {
<div [class.active]="item.isActive">{{ item.name }}</div>
}
}Diretiva com signal input e host
typescript
@Directive({
selector: '[appAutofocus]',
host: {
'(focus)': 'onFocus($event)'
}
})
export class AutofocusDirective {
readonly appAutofocus = input(true, { transform: booleanAttribute });
}独立组件(Angular 21中为默认配置,无需显式声明)
standalone: truetypescript
import { ChangeDetectionStrategy, Component, inject, input, signal } from '@angular/core';
@Component({
selector: 'app-page-header',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'app-page-header' },
imports: [RouterLink, NzBreadCrumbModule],
templateUrl: './page-header.component.html',
styleUrl: './page-header.component.scss'
})
export class PageHeaderComponent {
title = input.required<string>();
subtitle = input<string>();
breadcrumb = input<BreadcrumbItem[]>([]);
}基于HttpClient的服务
typescript
@Injectable({ providedIn: 'root' })
export class ProductService {
private readonly http = inject(HttpClient);
private readonly baseUrl = `${environment.apiBaseUrl}/api/products`;
list(): Observable<Product[]> {
return this.http.get<Product[]>(this.baseUrl);
}
}模板 – 流控制与绑定
html
@if (loading()) {
<nz-spin nzSimple></nz-spin>
} @else {
@for (item of items(); track item.id) {
<div [class.active]="item.isActive">{{ item.name }}</div>
}
}带signal输入和host绑定的指令
typescript
@Directive({
selector: '[appAutofocus]',
host: {
'(focus)': 'onFocus($event)'
}
})
export class AutofocusDirective {
readonly appAutofocus = input(true, { transform: booleanAttribute });
}Convenções
命名与编码约定
- Componentes: OnPush, , signal
host: { class: 'app-<nome>' }/input()quando fizer sentido; evitaroutput()/@Input()em código novo.@Output() - Templates: ,
@if,@for;@switch(ou estável) notrack item.id; sem@for/ngClass.ngStyle - Serviços: em vez de constructor injection;
inject()para serviços globais.providedIn: 'root' - Rotas: um arquivo ; guards em
app.routes.ts, importados nas rotas.shared/guards/ - Nomes: kebab-case para pastas e arquivos; prefixo para seletores de componentes/diretivas do app.
app
- 组件:使用OnPush变更检测,添加,合理使用signal类型的
host: { class: 'app-<名称>' }/input();新代码中避免使用output()/@Input()。@Output() - 模板:使用、
@if、@for;@switch循环中添加@for(或其他稳定标识);不使用track item.id/ngClass。ngStyle - 服务:使用替代构造函数注入;全局服务使用
inject()。providedIn: 'root' - 路由:统一使用文件;路由守卫放在
app.routes.ts,在路由中导入使用。shared/guards/ - 命名:文件夹和文件使用kebab-case命名;应用内的组件/指令选择器添加前缀。
app
Tratamento de erros e loading
错误处理与加载状态
- Chamadas HTTP: usar ,
catchError,finalizenos subscribes; mensagens viatimeoutquando fizer sentido.NzMessageService - Loading: signal no componente;
loadingou overlay compartilhado via[nzLoading]="loading()"quando for global.LoadingService
- HTTP请求:在订阅时使用、
catchError、finalize;必要时通过timeout展示提示信息。NzMessageService - 加载状态:在组件中使用signal类型的变量;局部加载使用
loading,全局加载使用[nzLoading]="loading()"提供的共享遮罩。LoadingService
Recursos adicionais
额外资源
- Estrutura detalhada e fluxo de dados: reference.md.
- 详细结构与数据流说明:reference.md。