AngularJS学习之四(分支之二):架构概述
架构概述
Angular是一个用于在HTML以及JavaScript或类似于编译为JavaScript的TypeScript语言中构建客户端应用程序的框架。
框架由几个库组成,其中一些是核心的,一些是可选的。
你通过编写HTML模板(带有Angular化的标签),编写组件类来管理这些模板,在services中添加应用程序逻辑,并在模块中组装组件和services。
然后,通过引导程序 root module来启动程序。Angular接管,在浏览器中显示您的应用内容,并根据您提供的指示响应用户的互动。
当然,这里有比已经提到的更多的内容。您将在随后的页面中了解那些详细信息。现在,我们先来关注大局。
这张架构图标识了Angular应用程序的八个主要的构建块:
学习这些构建块,你正在正确的方向前进。
此页面引用的代码都有一个可用的 live example。
模块
Angular应用程序是模块化的,Angular有一个名为Angular modules或NgModules的模块化系统。
Angular模块是一个大问题。本页介绍模块; 在Angular modules页会深度地讲解它们。
每个Angular app具有至少一个Angular模块类, the root module,传统命名为AppModule。
虽然root module可以是一个小应用程序的唯一模块,大多数应用程序有更多的功能模块,每个内聚的代码块(模块)专注于一个应用领域,一个工作流,或者是密切相关的功能集。
一个Angular模块,无论是根还是功能,都是一个带着 @NgModule
装饰器的类。
装饰器是修改JavaScript类的函数。Angular有许多装饰器将元数据附加到类上,以便知道这些类的含义和它们应该如何工作。在Learn more(了解更多)可以了解更多在网络上的装饰器的介绍。
NgModule
是一个装饰器函数,它接受一个元数据对象,其属性描述模块。最重要的属性是:
-
exports
- declarations(声明)的子集,在其他模块的组件模板中应该可见和可用。 -
imports
-其他模块,需要导出类,被本模块的组件模板声明。 -
providers
-本模块services的提供者提供给全局services集合; 它们在应用程序的所有部分都可访问。 -
bootstrap
-主要应用视图,称为根组件,承载所有其他应用视图。只有root module应设置这个bootstrap属性。
这里有一个简单的根模块:
app/app.module.ts
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 { }
AppComponent(怀疑这里写错了,应该是AppModule
)的export只是说明如何导出; 在该示例中实际上不是必需的。一个root
module没有理由export任何东西,因为其他组件不需要导入root module。
通过引导它的 root module来运行一个应用程序。在开发过程中,你可能会像这样的 main.ts 文件中引导AppModule。
app/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Angular 模块和JavaScript模块
Angular模块-一个装饰着@NgModule的类-是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 ships as a collection of JavaScript modules,ships as在这个地方应该如何理解呢?这么翻译,显得和上文不连贯)。你可以把它们理解成为一个库模块。
每一个Angular的库的名字都是以@angular这样的前缀开始的。
您可以用npm包管理器去安装它们,并且用JavaScript的import语句引入它们。
例如,从@angular/core库中引入Angular Component 装饰器就像这样:
import { Component } from '@angular/core';
您还可以使用JavaScript的import语句,从Angular库导入Angular modules:
import { BrowserModule } from '@angular/platform-browser';
在上面这个简单的 root module 例子里面,应用程序模块需要在 BrowserModule
里面的内容。
为了可以访问,像这样把它增加到@NgModule元数据中。
imports: [ BrowserModule ],
通过这样做,你就可以一起使用Angular和JavaScript的模块系统了。
因为这两个系统共享了“imports”和“exports”这两个词汇,所以很容易把它们搞混。那就先放在那里。随着时间的推移和经验的积累,这个混乱会搞清楚的。
在 Angular modules 页可以学到更多。
组件
一个控制一片屏幕的组件称为视图。
例如,下面的视图是由组件来控制的。
- 具有导航链接的 app root。
- 英雄列表。
- 英雄编辑器。
您定义了一个组件的应用程序逻辑 - 它支持视图-在类内部。这个类和视图通过属性和方法的API进行交互。
例如,这个HeroListComponent有一个heroes属性,用来返回它从一个服务中获取的一个的英雄的数组。 HeroListComponent也有一个selectHero()方法设置selectedHero属性,当用户点击选择从该列表中选择一个英雄。
app/hero-list.component.ts (class)
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; }
}
Angular在用户在应用程序中移动时(浏览时)创建,更新和销毁组件。您的app可以就像上面生命的ngOnInit()
那样,通过可选的lifecycle
hooks(生命周期的挂钩)在每一个时刻进行响应。
模板
您定义一个组件视图和它相伴随的模板。一个模板是一种HTML形式,告诉Angular如何渲染组件。
一个模板看起来像常规HTML,除了一些差异。下面是针对我们的HeroListComponent的一个模板HeroListComponent:
app/hero-list.component.html
<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>
虽然这个模板使用典型的HTML元素像<h2>
和 <p>
,它也有一些不同。类似 *ngFor
, {{hero.name}}
, (click)
,[hero]
,和<hero-detail>
的代码,使用的是
Angular's template syntax (Angular 模板语法)。
在模板的最后一行时,这个<hero-detail>标签是表示一个新组件的自定义元件,HeroDetailComponent。
该HeroDetailComponent是一个不同于你一直在看的HeroListComponent的组件。该HeroDetailComponent(代码没有显示)呈现一个特定的英雄的事实,即用户从所呈现的列表HeroListComponent中选择英雄。该HeroDetailComponent是HeroListComponent的孩子。
请注意如何<hero-detail>轻松地位于原生的HTML元素中。自定义组件与原生HTML在同一布局中无缝组合。
元数据
元数据告诉 Angular 如何去处理一个类。
Looking back at the code (沿着代码往回看)找到HeroListComponent
,你可以看到它仅仅是一个类。看不到任何框架的证据,里面也根本没有“Angular”。
事实上, HeroListComponent
真的只是一个类。 在你把它告诉Angular之前,它并不是一个组件。
想要告诉AngularHeroListComponent
是一个组件,那就给这个类附属上元数据。
在 TypeScript中,你使用一个decorator(装饰器)关键字去附属上元数据。这里有一些关于 HeroListComponent
的元数据:
app/hero-list.component.ts (metadata)
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: 'hero-list.component.html',
providers: [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}
这里有一个 @Component
装饰器,这会立刻把在它下面的这个类标识成一个组件类。,
这个@Component
装饰器采用了一个配置对象,这个对象具有Angular所需要的去创建和表示组件和它的视图的信息。
这里有一些可能的 @Component
配置选项:
moduleId
:为模块相关的URL(类似templateUrl),设置基础地址的源 (module.id
) 。
-
selector
:CSS 选择器告诉Angular去创建和插入一个这个组件的实例到在当前的HMTL中找到一个<hero-list>标签的地方。例如,如果一个app的HTML包含<hero-list></hero-list>
,然后Angular在这些标签中间插入一个HeroListComponent
视图的实例。 -
templateUrl
:这个模块的HTML模板的模块相关的地址,显示在上面。
providers
:为服务提供组件所需要的依赖注入提供者的数组(array of dependency injection providers) 。这是一个办法,能让Angular告诉这个组件构造器需要一个HeroService
这样它可以获取英雄的列表去显示。
在 @Component
中的元数据告诉Angular哪里去获得你为组件指定的主要的构件块。
模板、元数据、和组件一起描述了一个视图。
应用其它元数据装饰器也可以类似地指导Angular的行为。@Injectable
、 @Input
、和@Output
是一些最流行的装饰器。
这个架构(原文The architectural takeaway,暂时不确认如何解释) 就是你必须在你的代码中增加元数据,这样Angular才知道要做什么。
数据绑定
没有框架,你需要负责将数据值送入HTML控件中,并且把用户地反应变成行为和数据的更新。 手动编写这样的推/拉逻辑是令人乏味的,容易产生错误,是任何有经验的jQuery程序员可以作证的一场噩梦。
Angular支持数据绑定, 一种机制用于组件的部分和模板对应部分的协作。给模板HTML增加绑定标签去告诉Angular如何去连接双方。
就像图例所示,有四种格式的数据绑定语法。每一种格式都有一个方向,面向DOM的,从DOM出来的,或者是双方向的。
这个HeroListComponent
example 模板有三种格式:
app/hero-list.component.html (binding)
<li>{{hero.name}}</li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>
-
这个
{{hero.name}}
interpolation (插值)在<li>里面显示了组件的hero.name
属性值。 -
The
[hero]
property binding (属性绑定)传递selectedHero
的值从父亲的HeroListComponent
到孩子的HeroDetailComponent
的hero
属性。 -
这个
(click)
event binding (事件绑定)当用户点击一个英雄的名字的时候,调用组件的selectHero
方法。
双向数据绑定 是一个重要的第四种格式,使用ngModel
指示符,在单一的符号中捆绑着属性和事件绑定。这里有一个从 HeroDetailComponent
模板里面找到的例子:
app/hero-detail.component.html (ngModel)
<input [(ngModel)]="hero.name">
在双向绑定中,一个数据属性值使用属性绑定从组件流向输入框。使用事件绑定,用户的变化也反向流回组件,重设属性的最后的值。
Angular 在一次JavaScript 事件循环中处理所有 数据绑定,从应用组件树的根直到所有的孩子组件。
数据绑定在一个模板和它的组件之间担任着很重要的交流角色。
指示符
Angular模板是动态的。当Angular渲染它们的时候,它根据指示符的指导来改变DOM。
一个指示符是一个带有一个@Directive
装饰器的类。一个组件是一个 directive-with-a-template(指示符和一个模板); @Component
装饰器实际上是一个 @Directive
装饰器扩展后,带有面向模板的功能。
尽管一个组件技术上是一个指示符,但组件是这样的清晰,位于Angular应用程序的中心,这个结构化的总览还是选择将指示符和组件分开。
两个其它类型的指示符也存在:structural 和 attribute 指示符。
它们尝试去在一个元素标签中显示(就像attributes所做),有时是通过名字,但是更常见的是作为一个分配或者绑定的目标。
Structural 指示符通过增加、移除和替换在DOM中的元素来改变布局。
这个example template使用了两个内建的structural指示符:
app/hero-list.component.html (structural)
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>
Attribute指示符修改了一个存在的元素的显示或者表现。在模板中,他们看起来像规则的HTML属性,所有有这样的名字。
这个ngModel
指示符,实现了双向数据绑定,是一个attribute指示符的例子。 ngModel
通过设置它的显示值属性和对于变化事件的响应,修改了一个已经存在元素的表现
(典型的例如 <input>
) 。
app/hero-detail.component.html (ngModel)
<input [(ngModel)]="hero.name">
Angular还有一些指示符或者改变了布局结构 (例如,ngSwitch) 或者修改了DOM元素或组件的一些方面(例如, ngStyle 和ngClass).
当然,你也可以编写你自己的指示符。类似 HeroListComponent
的组件就是一种自定义指示符。
服务
服务 是一个广阔的分类,包含了任意的值,函数,或者你的应用需要的功能。
几乎所有事物都可以是一个服务。一个服务典型上是一个类,带着一个狭窄的定义好的目的。它将做指定的事情并且把它做好。
例子包括:
- 日志服务
- 数据服务
- 消息总线
- 税收计算器
- 应用程序配置
Angular关于服务没有什么特别的内容。Angular没有关于服务的定义。没有服务基础类,没有地方去注册服务。
但是服务是任何Angular程序的基础。组件是服务的大的消费者。
这里有一个关于服务的类的例子,输出日志到浏览器控制台:
app/logger.service.ts (class)
export class Logger {
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}
这里有一个 HeroService
获取英雄并且把他们返回到一个解析了的 Promise。这个HeroService
依赖这个Logger
服务,同时另外一个BackendService
处理着服务器通信的工作。
app/hero.service.ts (class)
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;
}
}
服务随处都是。
组件类应该精简。它们不从服务器端获取数据,校验用户输入,或者输出日志到控制台。它们把这些工作委托给服务。
一个组件的工作仅仅是提供用户体验(注:这里的用户可能指的是程序员)。它作为视图(被模板所渲染)和应用逻辑(经常包含一个model的符号)的媒介。一个好的组件代表着对于数据绑定的属性和方法。它把所有的别的重要的事情委托给服务。(注:原本 It delegates everything nontrivial to services.)
Angular并不强制要求实现这些原则。如果你用3000行写了一个“kitchen sink”的组件,它也并不会抱怨。
Angular通过很容易地把你地应用逻辑作为因素注入到服务中来帮助你遵循 这些原则,并且通过dependency injection 来使这些服务对于组件可用。
依赖注入
Dependency injection(依赖注入) 是一种方法使用它需要的完全的(fully-formed)依赖去支撑一个类的一个新的实例。大多数依赖是服务。Angular使用依赖注入去提供组件所需要的服务给新的组件。
Angular可以通过看到构造器参数的类型可以告诉一个组件需要哪些服务。例如,你的 HeroListComponent
需要一个HeroService
:
app/hero-list.component.ts (constructor)
constructor(private service: HeroService) { }
当Angular创建一个组件时,它首先会为组件需要的服务申请一个injector (注射器)。
一个注射器维持着它之前创建的服务实例的一个容器。如果一个请求的服务的实例不在容器中,注射器在返回服务给Angular之前会创建一个并且把它放入容器。当所有被请求的服务都被解析和返回,Angular会用这些服务作为参数去呼叫组件构造器。这就是依赖注入。
HeroService
注入的过程看起来有一点像这样:
如果这个注射器没有一个 HeroService
,它时怎么知道去创建一个呢?
简而言之,你必须在之前就用注射器注册一个 HeroService
提供者(provider)。一个提供者是一个可以创建或者返回一个服务的东西,最典型的就是服务类本身。
你可以在模块中或者在组件中注册提供者。
通常来说,给 root module 增加提供者,这样服务的同样实例在任何地方都有效。
app/app.module.ts (module providers)
providers: [
BackendService,
HeroService,
Logger
],
作为一种选择,在 @Component 元数据的providers
属性的组件层面注册:
app/hero-list.component.ts (component providers)
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: 'hero-list.component.html',
providers: [ HeroService ]
})
在组件层面注册意味着你用那个组件的每一个新实例去获取一个新的服务的实例。
关于依赖注入需要记住的点:
-
依赖注入是装配到Angular结构上的,可以在任何地方使用。
-
这个注射器是主要的装置。
- 一个注射器维护者一个它创建的服务实例的容器。
- 一个注射器可以通过provider创建新的服务实例。
-
一个提供者是创建服务的秘诀。
-
使用注射器去注册提供者。
总结
你已经学到了Angular应用的八个主要构件块的基础了。
那是在Angular应用中的任何东西的基础,从这里起步应该是绰绰有余了。(注:原文是and it's more than enough to get going. )但是它不包括所有你需要知道的。
这里有一个简要的,字母排序的,重要的Angular功能和服务的列表。它们中的大多数会在本套文档中覆盖(或者即将是)。
Animations(动画):不需要对动画技术或CSS的深度掌握的,使用Angualr动画库开发动画组件行为。
Change detection(变更检测):变更检测文档会覆盖Angular是如何确定一个组件的属性值发生了改变,什么时候去更新屏幕,并且它是如何使用zones 去插入异步活动和运行它的变更检测策略。
Events(事件):事件文档将会覆盖如何使用组件和服务去触发事件去发布事件或者订阅事件。(注:原文是The events documentation will cover how to use components and services to raise events with mechanisms for publishing and subscribing to events.)
Forms(表单):使用基于HTML验证和脏检查来支持复杂数据场景。
HTTP:用HTTP客户端来和一个服务器进行通信去获取数据,保存数据,调用服务端行为。
Lifecycle hooks(生命周期钩子):通过实现生命周期钩子接口,在一个组件的生命周期的关键时刻嵌入,从构建到销毁。
Pipes(管道):在模板中使用管道去提升用户体验,把值转换成一种显示。考虑这个
currency
管道表达式:
price | currency:'USD':true
它把42.33的价格显示成
$42.33
。Router(路由器):在客户端程序中的页面间导航,并且绝不会离开浏览器。
Testing(测试):使用Angular Testing Platform在你的应用部分运行单元测试,就像它们在与Angular结构交互。
posted on 2017-01-08 18:04 chaiyu2002 阅读(291) 评论(0) 编辑 收藏 举报