学习资料来自 Angular.cn 与 Angular.io。
架构概览
Angular 应用的实现方式:
- 用 Angular 扩展语法编写 HTML 模板
- 用组件类管理这些模板
- 用服务添加应用逻辑
- 用模块打包发布组件与服务
- 通过引导根模块来启动该应用
- Angular 在浏览器中接管、展现应用的内容,并根据我们提供的操作指令响应用户的交互
Angular 应用中的8个主要构造块:
- 模块 (module)
- 组件 (component)
- 模板 (template)
- 元数据 (metadata)
- 数据绑定 (data binding)
- 指令 (directive)
- 服务 (service)
- 依赖注入 (dependency injection)
模块
每个 Angular 应用至少有一个根模块,习惯上命名为 AppModule
。
大多数应用会有很多特性模块,每个模块都是一个内聚的代码块专注于某个应用领域、工作流或紧密相关的功能。
Angular 模块是一个带有 @NgModule
装饰器的类。
装饰器是用来修饰 JavaScript 类的函数。
NgModule
是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。其中最重要的属性是:
-
declarations
声明本模块中拥有的视图类。Angular 有三种视图类:组件、指令和管道。 -
exports
declarations 的子集,可用于其它模块的组件模板。 -
imports
本模块声明的组件模板需要的类所在的其它模块。 -
providers
服务的创建者,并加入到全局服务列表中,可用于应用任何部分。 -
bootstrap
指定应用的主视图(称为根组件),它是所有其它视图的宿主。只有根模块才能设置bootstrap
属性。
示例:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ],
providers: [ Logger ],
declarations: [ AppComponent ],
exports: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
说明:上例中
export
语句仅用于演示,并非必须。根模块不需要导出任何东西,因为其它组件不需要导入根模块。
引导根模块。通常在一个 main.ts
文件中引导 AppModule
,例如:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Angular 模块与 JavaScript 模块
注意:JavaScript 的模块系统与 Angular 的模块系统完全不同且完全无关。
JavaScript 中,每个文件是一个模块,文件中定义的所有对象都从属于那个模块。 通过 export
关键字,模块可以把它的某些对象声明为公共的。 其它 JavaScript 模块可以使用 import
语句来访问这些公共对象。
示例:
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
export class AppModule { }
更多关于 JavaScript 模块的知识。
Angular 模块库
Angular 提供了一组 JavaScript 模块。可以把它们看做库模块。每个 Angular 库的名字都带有 @angular
前缀。
组件
组件负责控制屏幕上的一小块区域,我们称之为视图。我们在类中定义组件的应用逻辑,为视图提供支持。
模板
我们通过组件的自带的模板来定义组件视图。模板以 HTML 形式存在。
元数据
元数据告诉 Angular 如何处理一个类。在 TypeScript 中,我们用装饰器 (decorator) 来附加元数据。
数据绑定
四种绑定形式:
- 插值表达式
- {{hero.name}}
- 属性绑定
- 事件绑定
- 双向数据绑定
指令
Angular 模板是动态的。当 Angular 渲染它们时,它会根据指令提供的操作对 DOM 进行转换。
指令的类型:
- 组件
组件是一个带模板的指令。 - 属性型指令
修改一个现有元素的外观或行为。 - 结构型指令
通过在 DOM 中添加、移除和替换元素来修改布局。
服务
服务是一个广义范畴,包括:值、函数,或应用所需的特性。
几乎任何东西都可以是一个服务。 典型的服务是一个类,具有专注的、明确的用途。
服务类示例 src/app/logger.service.ts
:
export class Logger {
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}
服务类示例 src/app/hero.service.ts
:
export class HeroService {
private heroes: Hero[] = [];
constructor(
private backend: BackendService,
private logger: Logger) { }
getHeroes() {
this.backend.getAll(Hero).then( (heroes: Hero[]) => {
this.logger.log(`Fetched ${heroes.length} heroes.`);
this.heroes.push(...heroes); // fill cache
});
return this.heroes;
}
}
组件类应保持精简。组件本身不从服务器获得数据、不进行验证输入,也不直接往控制台写日志。 它们把这些任务委托给服务。
依赖注入
“依赖注入”是提供类的新实例的一种方式,还负责处理好类所需的全部依赖。大多数依赖都是服务。 Angular 使用依赖注入来提供新组件以及组件所需的服务。
当 Angular 创建组件时,会首先为组件所需的服务请求一个注入器 (injector)。
注入器维护了一个服务实例的容器,存放着以前创建的实例。 如果所请求的服务实例不在容器中,注入器就会创建一个服务实例,并且添加到容器中,然后把这个服务返回给 Angular。 当所有请求的服务都被解析完并返回时,Angular 会以这些服务为参数去调用组件的构造函数。 这就是依赖注入 。
如果注入器还没有服务(例如 HeroService
),它怎么知道该如何创建一个呢?我们必须先用注入器(injector)为 HeroService
注册一个提供商(provider)。
通常会把提供商添加到根模块上,以便在任何地方都使用服务的同一个实例。
providers: [
BackendService,
HeroService,
Logger
],
也可以在 @Component
元数据中的 providers
属性中把它注册在组件层:
@Component({
selector: 'hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
注意:将提供商注册在组件级表示该组件的每一个新实例都会有一个服务的新实例。
总结
了解 Angular 应用的实现方式及构造块概念,理解其工作原理。