背景
由于最近工作的需要,后端同学需要开发后台系统的web界面,所以调研了一些主流的组件化前端框架,比如reactjs,vuejs,angularjs等。其中angularjs属于具有完整体系的一个web框架,也就是说不需要额外的前端库支持,就可以完成一个前端应用。对于不是很了解前端技术栈的后端同学来说比较适合,同时与AngularJS整合的Typescript,它的语法也和Java比较接近,而且是强类型的语言。
Angular框架核心模型
当然这篇文章并不是用来谈论前端框架的技术选型的,而是想从Angular框架的角度来讲讲前端开发领域的模型是什么?
从这个架构图上我们可以看到里面有很多概念:
- 模块(Module)
- 组件(Components)
- 模版(Templates)
- 元数据(Metadata)
- 数据绑定(Data binding)
- 指令(Directives)
- 服务(Services)
- 依赖注入(Dependency injection)
这里很多概念可能后端开发同学反而更加熟悉,比如服务和依赖注入。当然我们今天主要是关注核心模型,所以我用模型图把概念里面的模型单拎出来:
这里我们忽略了元数据和依赖注入这两个概念,这两个我们可以理解成双向绑定的声明方式(元数据),服务的调用方式(依赖注入)所以不放入核心模型中探讨。
模块(Modules)
所谓模块其实是语言级的概念,因为javascript并没有语言级的模块支持,然而几乎所有的js框架中都有这个概念,甚至有很多专门用于实现js模块化的框架。
所以一个模块不光可以包含组件(Components)(实现层面就是一个用@NgModule声明的class),服务(Services)(实现层面是一个普通的class),还可以包含值变量、函数、类等等。
当然Angular的模块是要依赖语言的包机制实现的,一个典型的Angular模块:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ], // 依赖的外部类
providers: [ Logger ], // 提供出去的服务
declarations: [ AppComponent ], // 属于这个模块的视图类,比如components、directives、pipes
exports: [ AppComponent ], // 提供外部访问的类
bootstrap: [ AppComponent ] // 声明模块的根组件
})
export class AppModule { }
AngularJS里面没有applicaiton的概念,所以就用一个根模块来表达,如下:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
AngularJS里面也没有显示libraries的概念,所以也是用模块来表达的。
组件(Components)
一个组件主要是控制着屏幕上的一部分视图,比如整个应用的导航链接。
export class HeroListComponent implements OnInit {
heroes: Hero[];
selectedHero: Hero;
constructor(private service: HeroService) { }
ngOnInit() {
this.heroes = this.service.getHeroes();
}
selectHero(hero: Hero) { this.selectedHero = hero; }
}
在编写组件的时候我们需要关注组件生命周期的扩展钩子:
模版(Templates)
一个组件的视图就是一个模版,它包含一个html的框架用来告诉Angular如何渲染组件。
<h2>Hero List</h2>
<p><i>Pick a hero from the list</i></p>
<ul>
<li *ngFor="let hero of heroes" (click)="selectHero(hero)">
{{hero.name}}
</li>
</ul>
<hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>
模版可以包含指令,还可以引用子组件:
指令(Directives)
指令是用来告诉Angular框架如何渲染(转化成DOM结构)。一个指令就是带有@Directive声明的一个类。一个组件本质上也就是一个关联模版的指令。
除了组件型的指令以外,还有结构型的指令,比如下面的模版就包含两个框架自带的结构型的指令:
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>
其次还有一种属性型的指令,用于扩展html标签的属性功能,比如提供值绑定能力。
<input [(ngModel)]="hero.name">
当然可能还有其他类型的指令,比如处理布局等等,当然你也可以自定义新的指令。
服务(Services)
服务很大程度上就是一个普通的类,但是我们一般会给它分配一个明确的职责,让它提供某一种特定的能力。比如:
- 日志服务
- 数据服务
- 消息总线
- 税务计算器
- 应用程序配置
日志服务样例:
export class Logger {
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}
Angular没有明确服务的约束,但是提供了对服务的依赖注入功能:
constructor(private service: HeroService) { }
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
框架就会做自动匹配,效果如下图:
总结
我们今天主要从领域模型的角度去了解AngularJS的总体架构,核心模型主要有模块、组件、模版、指令、服务,从中我们可以感觉到从核心模型的角度去理解一个复杂系统的设计是很有帮助的,往往能很快的抓到系统设计的重心。