1,
架构概览
Angular 的基本构造块是 NgModule,它为组件提供了编译的上下文环境。 NgModule 会把相关的代码收集到一些功能集中。Angular 应用就是由一组 NgModule 定义出的。 应用至少会有一个用于引导应用的根模块,通常还会有很多特性模块。
-
组件定义视图。视图是一组可见的屏幕元素,Angular 可以根据你的程序逻辑和数据来选择和修改它们。 每个应用都至少有一个根组件。
-
组件使用服务。服务会提供那些与视图不直接相关的功能。服务提供商可以作为依赖被注入到组件中, 这能让你的代码更加模块化、更加可复用、更加高效。
-
组件类的元数据将组件类和一个用来定义视图的模板关联起来。 模板把普通的 HTML 和 Angular 指令与绑定标记(markup)组合起来,这样 Angular 就可以在呈现 HTML 之前先修改这些 HTML。
-
服务类的元数据提供了一些信息,Angular 要用这些信息来让组件可以通过依赖注入(DI)使用该服务。
模块
Angular 定义了 NgModule
,它和 JavaScript(ES2015) 的模块不同而且有一定的互补性。 NgModule 为一个组件集声明了编译的上下文环境,它专注于某个应用领域、某个工作流或一组紧密相关的能力
NgModule 可以将其组件和一组相关代码(如服务)关联起来,形成功能单元。
每个 Angular 应用都有一个根模块,通常命名为 AppModule
。根模块提供了用来启动应用的引导机制。 一个应用通常会包含很多功能模块。
组件
每个 Angular 应用都至少有一个组件,也就是根组件,它会把组件树和页面中的 DOM 连接起来。 每个组件都会定义一个类,其中包含应用的数据和逻辑,并与一个 HTML 模板相关联,该模板定义了一个供目标环境下显示的视图。
模板、指令和数据绑定
模板会把 HTML 和 Angular 的标记(markup)组合起来,这些标记可以在 HTML 元素显示出来之前修改它们。 模板中的指令会提供程序逻辑,而绑定标记会把你应用中的数据和 DOM 连接在一起。 有两种类型的数据绑定:
-
事件绑定让你的应用可以通过更新应用的数据来响应目标环境下的用户输入。
-
属性绑定让你将从应用数据中计算出来的值插入到 HTML 中。
在视图显示出来之前,Angular 会先根据你的应用数据和逻辑来运行模板中的指令并解析绑定表达式,以修改 HTML 元素和 DOM。 Angular 支持双向数据绑定,这意味着 DOM 中发生的变化(比如用户的选择)同样可以反映回你的程序数据中。
你的模板也可以用管道转换要显示的值以增强用户体验。比如,可以使用管道来显示适合用户所在地区的日期和货币格式。 Angular 为一些通用的转换提供了预定义管道,你还可以定义自己的管道。
2,
NgModule 简介
ngular 应用是模块化的,它拥有自己的模块化系统,称作 NgModule。 一个 NgModule 就是一个容器,用于存放一些内聚的代码块,这些代码块专注于某个应用领域、某个工作流或一组紧密相关的功能。 它可以包含一些组件、服务提供商或其它代码文件,其作用域由包含它们的 NgModule 定义。 它还可以导入一些由其它模块中导出的功能,并导出一些指定的功能供其它 NgModule 使用。
每个 Angular 应用都至少有一个 NgModule 类,也就是根模块,它习惯上命名为 AppModule
,并位于一个名叫 app.module.ts
的文件中。引导这个根模块就可以启动你的应用。
虽然小型的应用可能只有一个 NgModule,不过大多数应用都会有很多特性模块。应用的根模块之所以叫根模块,是因为它可以包含任意深度的层次化子模块。
@NgModule
元数据
NgModule 是一个带有 @NgModule()
装饰器的类。@NgModule()
装饰器是一个函数,它接受一个元数据对象,该对象的属性用来描述这个模块。其中最重要的属性如下。
-
declarations
(可声明对象表) —— 那些属于本 NgModule 的组件、指令、管道。 -
exports
(导出表) —— 那些能在其它模块的组件模板中使用的可声明对象的子集。 -
imports
(导入表) —— 那些导出了本模块中的组件模板所需的类的其它模块。 -
providers
—— 本模块向全局服务中贡献的那些服务的创建器。 这些服务能被本应用中的任何部分使用。(你也可以在组件级别指定服务提供商,这通常是首选方式。) -
bootstrap
—— 应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有根模块才应该设置这个bootstrap
属性。
NgModule 和组件
NgModule 为其中的组件提供了一个编译上下文环境。根模块总会有一个根组件,并在引导期间创建它。 但是,任何模块都能包含任意数量的其它组件,这些组件可以通过路由器加载,也可以通过模板创建。那些属于这个 NgModule 的组件会共享同一个编译上下文环境。
组件及其模板共同定义视图。组件还可以包含视图层次结构,它能让你定义任意复杂的屏幕区域,可以将其作为一个整体进行创建、修改和销毁。 一个视图层次结构中可以混合使用由不同 NgModule 中的组件定义的视图。 这种情况很常见,特别是对一些 UI 库来说。
3,
组件简介
件控制屏幕上被称为视图的一小片区域。比如,教程中的下列视图都是由一个个组件所定义和控制的:
-
带有导航链接的应用根组件。
-
英雄列表。
-
英雄编辑器。
你在类中定义组件的应用逻辑,为视图提供支持。 组件通过一些由属性和方法组成的 API 与视图交互
组件的元数据
@Component
装饰器会指出紧随其后的那个类是个组件类,并为其指定元数据。 在下面的范例代码中,你可以看到 HeroListComponent
只是一个普通类,完全没有 Angular 特有的标记或语法。 直到给它加上了 @Component
装饰器,它才变成了组件。
组件的元数据告诉 Angular 到哪里获取它需要的主要构造块,以创建和展示这个组件及其视图。 具体来说,它把一个模板(无论是直接内联在代码中还是引用的外部文件)和该组件关联起来。 该组件及其模板,共同描述了一个视图。
除了包含或指向模板之外,@Component
的元数据还会配置要如何在 HTML 中引用该组件,以及该组件需要哪些服务等等。
模板与视图
你要通过组件的配套模板来定义其视图。模板就是一种 HTML,它会告诉 Angular 如何渲染该组件。
模板语法
模板很像标准的 HTML,但是它还包含 Angular 的模板语法,这些模板语法可以根据你的应用逻辑、应用状态和 DOM 数据来修改这些 HTML。 你的模板可以使用数据绑定来协调应用和 DOM 中的数据,使用管道在显示出来之前对其进行转换,使用指令来把程序逻辑应用到要显示的内容上。
这个模板使用了典型的 HTML 元素,比如 <h2>
和 <p>
,还包括一些 Angular 的模板语法元素,如 *ngFor
,{{hero.name}}
,click
、[hero]
和 <app-hero-detail>
。这些模板语法元素告诉 Angular 该如何根据程序逻辑和数据在屏幕上渲染 HTML。
-
*ngFor
指令告诉 Angular 在一个列表上进行迭代。 -
{{hero.name}}
、(click)
和[hero]
把程序数据绑定到及绑定回 DOM,以响应用户的输入。更多内容参见稍后的数据绑定部分。 -
模板中的
<app-hero-detail>
标签是一个代表新组件HeroDetailComponent
的元素。HeroDetailComponent
(代码略)定义了HeroListComponent
的英雄详情子视图。 注意观察像这样的自定义组件是如何与原生 HTML 元素无缝的混合在一起的。
数据绑定
如果没有框架,你就要自己负责把数据值推送到 HTML 控件中,并把来自用户的响应转换成动作和对值的更新。 手动写这种数据推拉逻辑会很枯燥、容易出错,难以阅读 —— 有前端 JavaScript 开发经验的程序员一定深有体会。
Angular 支持双向数据绑定,这是一种对模板中的各个部件与组件中的各个部件进行协调的机制。 往模板 HTML 中添加绑定标记可以告诉 Angular 该如何连接它们。
<li>{{hero.name}}</li>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<li (click)="selectHero(hero)"></li>
-
{{hero.name}}
插值表达式在<li>
标签中显示组件的hero.name
属性的值。 -
[hero]
属性绑定把父组件HeroListComponent
的selectedHero
的值传到子组件HeroDetailComponent
的hero
属性中。 -
当用户点击某个英雄的名字时,
(click)
事件绑定会调用组件的selectHero
方法。
双向数据绑定(主要用于模板驱动表单中),它会把属性绑定和事件绑定组合成一种单独的写法。下面这个来自
<input [(ngModel)]="hero.name">
在双向绑定中,数据属性值通过属性绑定从组件流到输入框。用户的修改通过事件绑定流回组件,把属性值设置为最新的值。
Angular 在每个 JavaScript 事件循环中处理所有的数据绑定,它会从组件树的根部开始,递归处理全部子组件。
管道
Angular 的管道可以让你在模板中声明显示值的转换逻辑。 带有 @Pipe
装饰器的类中会定义一个转换函数,用来把输入值转换成供视图显示用的输出值。
Angular 自带了很多管道,比如 date 管道和 currency 管道,完整的列表参见 Pipes API 列表。你也可以自己定义一些新管道。
要在 HTML 模板中指定值的转换方式,请使用 管道操作符 (|)。
{{interpolated_value | pipe_name}}
你可以把管道串联起来,把一个管道函数的输出送给另一个管道函数进行转换。 管道还能接收一些参数,来控制它该如何进行转换
4,
服务与依赖注入简介
服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。它应该做一些具体的事,并做好。
Angular 把组件和服务区分开,以提高模块性和复用性。 通过把组件中和视图有关的功能与其他类型的处理分离开,你可以让组件类更加精简、高效。
理想情况下,组件的工作只管用户体验,而不用顾及其它。 它应该提供用于数据绑定的属性和方法,以便作为视图(由模板渲染)和应用逻辑(通常包含一些模型的概念)的中介者。
组件应该把诸如从服务器获取数据、验证用户输入或直接往控制台中写日志等工作委托给各种服务。通过把各种处理任务定义到可注入的服务类中,你可以让它被任何组件使用。 通过在不同的环境中注入同一种服务的不同提供商,你还可以让你的应用更具适应性。
Angular 不会强迫你遵循这些原则。Angular 只会通过依赖注入来帮你更容易地将应用逻辑分解为服务,并让这些服务可用于各个组件中。
DI 被融入 Angular 框架中,用于在任何地方给新建的组件提供服务或所需的其它东西。 组件是服务的消费者,也就是说,你可以把一个服务注入到组件中,让组件类得以访问该服务类。
在 Angular 中,要把一个类定义为服务,就要用 @Injectable()
装饰器来提供元数据,以便让 Angular 可以把它作为依赖注入到组件中。 同样,也要使用 @Injectable()
装饰器来表明一个组件或其它类(比如另一个服务、管道或 NgModule)拥有一个依赖。
-
注入器是主要的机制。Angular 会在启动过程中为你创建全应用级注入器以及所需的其它注入器。你不用自己创建注入器。
-
该注入器会创建依赖、维护一个容器来管理这些依赖,并尽可能复用它们。
-
提供商是一个对象,用来告诉注入器应该如何获取或创建依赖。
你的应用中所需的任何依赖,都必须使用该应用的注入器来注册一个提供商,以便注入器可以使用这个提供商来创建新实例。 对于服务,该提供商通常就是服务类本身。
当 Angular 发现某个组件依赖某个服务时,它会首先检查是否该注入器中已经有了那个服务的任何现有实例。如果所请求的服务尚不存在,注入器就会使用以前注册的服务提供商来制作一个,并把它加入注入器中,然后把该服务返回给 Angular。
当所有请求的服务已解析并返回时,Angular 可以用这些服务实例为参数,调用该组件的构造函数
提供服务
对于要用到的任何服务,你必须至少注册一个提供商。服务可以在自己的元数据中把自己注册为提供商,这样可以让自己随处可用。或者,你也可以为特定的模块或组件注册提供商。要注册提供商,就要在服务的 @Injectable()
装饰器中提供它的元数据,或者在@NgModule()
或 @Component()
的元数据中。
-
默认情况下,Angular CLI 的
ng generate service
命令会在@Injectable()
装饰器中提供元数据来把它注册到根注入器中。本教程就用这种方法注册了 HeroService 的提供商:
@Injectable({
providedIn: 'root',
})
当你在根一级提供服务时,Angular 会为 HeroService 创建一个单一的共享实例,并且把它注入到任何想要它的类中。这种在 @Injectable
元数据中注册提供商的方式还让 Angular 能够通过移除那些从未被用过的服务来优化大小。
-
当你使用特定的 NgModule 注册提供商时,该服务的同一个实例将会对该 NgModule 中的所有组件可用。要想在这一层注册,请用
@NgModule()
装饰器中的providers
属性:
content_copy@NgModule({
providers: [
BackendService,
Logger
],
...
})
-
当你在组件级注册提供商时,你会为该组件的每一个新实例提供该服务的一个新实例。 要在组件级注册,就要在
@Component()
元数据的providers
属性中注册服务提供商。