Skip to main content

与传统 Angular 开发模式的对比

本节站在「有一定 Angular 经验」的视角,比较基于 @rydeen/angular-framework 的架构化写法,与常见的 面向组件 / 面向过程式 开发方式之间的差异与优势。

1. 总览对比表​

维度传统 Angular 写法基于框架的架构化写法
目录结构按功能或组件随意划分,规则不统一明确区分 core / shared / features,与 Java 服务端分层一致
HTTP 调用各 Service 各自注入 HttpClient,手动拼接 URL/Headers统一继承 AbstractService/AbstractAppService,自动处理基址、签名、防重
返回结构Observable<any> / Observable<any[]> 等Promise<ResultVO<T>> / Page<T>,与 Java 端 ResultVO 完全对齐
页面基类组件直接继承 Component所有页面继承 AbstractAppPage,统一导航、消息提示与事件订阅
UI 事件处理模板中到处是 (click)="onXXX()",方法与 DOM 紧耦合通过 data-id + 统一 onClick/onChange/onSubmit 分发
状态与事件多处手写 Subject/EventEmitter,缺乏统一规范使用 EventManager + EventNameEnum/AppEvent 集中管理事件
并发控制与本地存储临时变量 + flag 控制,逻辑分散提供 LocalStorage、ReentrantLock 等通用工具
学习曲线简单开始,复杂业务时需要自行搭建架构初期需要理解抽象基类与 Url/ResultVO,但后续开发体验更稳定

2. HTTP 调用:从 HttpClient 到 AbstractService​

传统写法示例​

@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}

getUsers(): Observable<User[]> {
const token = localStorage.getItem('token');
return this.http.get<User[]>('/api/users', {
headers: { Authorization: token ?? '' },
});
}
}

问题:

  • URL、Header、错误处理分散在各个 Service 中
  • 与后端的 ResultVO<T> 协议不一致,需要在业务层反复拆封装

基于框架的写法​

@Injectable({ providedIn: 'root' })
export class UserService extends AbstractAppService {
async list(): Promise<ResultVO<User[]>> {
return await this.request<User[]>(AppUrl.Users.List);
}
}

优势:

  • Service 只关心「调用哪个 Url」,其它能力(设备指纹、防重试、加解密、Loading)全部下沉到基类中。
  • 类型与 Java 端使用的 ResultVO<T>、Page<T> 一致,前后端认知统一。

3. 页面结构:从「组件各自为政」到「统一页面基类」​

传统页面​

@Component({...})
export class UsersComponent implements OnInit {
loading = false;

constructor(
private snackBar: MatSnackBar,
private router: Router,
private userService: UserService,
) {}

async load() {
this.loading = true;
try {
const data = await lastValueFrom(this.userService.getUsers());
// ...
} finally {
this.loading = false;
}
}

showSuccess(msg: string) {
this.snackBar.open(msg, undefined, { duration: 2000 });
}
}

问题:

  • 每个页面重复处理 Loading / SnackBar / 导航等共性逻辑
  • 日后变更交互样式(如 Loading 改成全局遮罩)时,需要逐个修改页面

基于 AbstractAppPage 的页面​

@Component({...})
export class UsersPage extends AbstractAppPage {
constructor(private readonly userService: UserService) {
super();
}

async ngOnInit() {
this.registerHandler('users.queryBtn', async () => {
const result = await this.userService.list();
if (result.code === '200') {
await this.success('users.load.success', { isKey: true });
} else {
await this.error(result.msg ?? 'users.load.error');
}
});
}
}

模板中:

<button
mat-raised-button
color="primary"
data-id="users.queryBtn"
(click)="onClick($event)">
查询用户
</button>

优势:

  • 页面类只处理业务逻辑与事件注册,导航/消息/事件总线在抽象基类中统一实现。
  • 模板与逻辑通过 data-id 解耦,后续改模板不会牵连过多 TS 代码。

4. 架构视角比较(Mermaid)​

可以看到,在框架化模式下:

  • HttpClient 只出现在 AbstractService 这一层
  • 事件与 Loading 都通过 EventManager 打通页面与服务
  • 业务代码的「依赖面」更窄,更容易做测试与重构

5. 适用场景建议​

更适合使用本框架的场景:

  • 前后端都在使用润典技术栈,希望在 API 规范、类型与错误处理上保持一致
  • 中大型业务系统(如收银、点餐、运营后台等),对可维护性和统一体验有较高要求
  • 有大量全局横切需求:权限、埋点、Loading、错误提示、国际化等

可以考虑保持简单写法的场景:

  • Demo 级别的 PoC、小工具类应用
  • 只需要少量接口、交互简单,对架构统一性要求不高

在实际项目中,你也可以按「核心功能使用框架、边缘模块保留简单写法」的方式渐进式引入。