angular架构总览

一个完整的Angular应用主要由6个重要部分构成,分别是:组件、模板、指令、服务、依赖注入和路由。这些组成部分各司其职,而又紧密协作,它们的关系如图所示。

与用户直接交互的是模板视图,模板视图并不是独立的模块,它是组成组件的要素之一。另一个要素是组件类,用以维护组件的数据模型及功能逻辑。

路由的功能是控制组件的创建和销毁,从而驱使应用界面跳转切换。指令与模板相互关联,最重要的作用是增强模板特性,间接扩展了模板的语法。

服务是封装若干功能逻辑的单元,这个功能逻辑可以通过依赖注入机制引入到组件内部,作为组件功能的扩展。

在Angular的应用接收用户指令、加工处理后输出相应视图的过程中,组件始终处于这个交互的出入口,这正是Angular基于组件设计的体现。组件承载着Angular的核心功能,所以接下来的内容将从组件开始,逐步揭开Angular框架的神秘面纱。

组件Component

angular应用基于组件设计,每个组件也并不是孤立存在的,组件与组件之间通常会构成一个树状的结构,父子组件之间存在着双向的数据流动。要理解数据是怎样流动的,首先要了解组件之间的调用方式。简单地说,组件的外在形态就是自定义标签,所以组件的调用实际体现在模板标签里的引用上。

组件之间可以通过模板来形成关系,组件可以通过@Input和@Output来对外暴露接口。在模板里面“[property]”表示属性绑定,数据从父组件流向子组件,“(event)”表示事件绑定,数据从子组件流向父组件。在angular模板里面可以直接引用组件类中的字段,组件类和模板之间的数据交互称为“数据绑定”。前面说的属性绑定和事件绑定也是数据绑定的范畴,数据在组件间的流动需要一种机制来推动实现,需要一种驱动力,这个驱动力就是angular的变化监测机制。angular是一个响应式的框架,每次数据变动几乎都能实时处理,并更新到视图(模板),那么Angular是如何感知数据对象发生了变动呢?ES5提供了getter/setter语言接口来捕获对象变动,然而Angular并没有采用。Angular是在适当的时机中检验对象的值是否被改动的,这个适当的时机并不是指固定的某个频率,而通常是在用户操作事件(如单击)或者setTimeout、XHR回调等这些异步事件触发之后。Angular捕获这些异步事件的工作是通过Zone.js库实现的(关于Zones的内容在后面会展开描述)。每个组件背后都维护着一个独立的变化监测器,这个变化监测器记录着所属组件的数据变更状态。由于应用是以组件树的形式组织的,因此每个应用也都对应着一棵变化监测树。当Zones捕获到某异步事件后,它会通知Angular执行变化监测操作,每次变化监测操作都始于根组件,并以深度优先的原则向叶子组件遍历执行。angular5提供了不依赖Zones的写法,后面会有详细的描述。Angular强大的数据变化监测机制使得开发者不必关心数据何时变动,结合数据绑定实现模板视图实时更新。变化监测机制提供了数据自动更新功能,若此时需要手动捕获变化事件做一些额外处理,可以吗?答案是肯定的。Angular还提供了完善的生命周期钩子给开发者调用,如ngOnChanges可以满足刚提到的捕获变化事件的要求,ngOnDestroy可以在组件销毁前做一些清理工作,等等。

模板Template

模板的语法基于HTML,Angular为模板定义了一套强大的语法,数据绑定是模板最基本的功能。除了上述提到的属性绑定和事件绑定,插值也是很常见的数据绑定的方法,向这样:{{item}},这个在vue里面也是一样的用法。

插值的变量上下文是组件类本身,它是一种单向的数据传递,从组件类流向模板。上面提到的三种数据绑定都是单向的流动的。但是在某些场景需要双向的数据绑定,例如表单,angular结合了属性绑定和事件绑定,提供了如下的语法来支持双向绑定:

<input [(ngModel)]="item"></input>

[()]是实现双向绑定的语法糖。ngModel是辅助实现双向绑定的内置指令。上述代码执行后,input控件和item之间就形成了双向的数据关联,input的值发生改变时,会自动赋值到item上,item的值发生改变时也会更新input的值。

数据绑定解决了数据的传递和展示,而针对数据的格式化显示,angular提供了一种称作管道的功能来解决。管道使用竖线“|”来表示:

<span>{{item | phone}}</span>

如上可将item的值显示为phone的格式。比如item的值是13844443333,使用管道操作符可以将item的显示方式转换成138-4444-3333.

指令Directive

指令与模板关系密切,指令可以与DOM进行灵活交互,它或者改变样式,或者改变布局。指令的范畴很广,实际上组件也是指令的一种。组件与一般指令的区别在于:组件带有单独的模板,即DOM元素;而一般的指令作用在已有的DOM元素上。一般的指令分为两种:结构指令和属性指令。

结构指令能够添加、删除、修改DOM从而改变布局,例如ngIf:

<button *ngIf="canEdit">编辑<button>

当canEdit的值为true时,可以点击按钮,如果为false时,该按钮会从DOM中删除,不显示。

注意:*号很重要,不能丢掉,它是结构型指令的重要组成部分。

属性指令用来改变元素的外观或行为,使用起来跟普通的HTML元素属性非常相似,例如ngStyle指令,用于动态计算样式值。示例代码如下:

<span [ngStyle]="setStyle()">{{contact.name}}</span>

<span>标签的样式由setStyle()方法计算得出。setStyle()是组件类的函数成员,返回一个计算好的样式对象:

关于指令的用法会在后面进行详细的描述,指令的强大之处就在于可以自定义指令,能够最大限度的实现UI层面的逻辑。

服务Service

服务是封装单一功能的单元,类似于工具库,常被引用到组件内部,作为组件的功能扩展。那么服务包含什么?它可以是一个简单的字符串或JSON数据,也可以是一个函数,甚至是一个类,几乎所有的对象都可以封装成服务。
封装成独立模块的服务可以被许多不同的组件重复使用,这就是服务的设计原则。HTTP是angular常用的服务,它封装了一系列异步接口,但与一般的接口不同,它对外暴露的是Reactive programing规范的接口,基于RXJS实现,严格贯彻响应式编程的思想。所以会在后面详细介绍HTTP的同时,也会详细的介绍RXJS这个响应式编程的框架。

依赖注入Dependency Injecting

依赖注入一直都是angular的卖点所在,当一个组件需要某个服务时,只需要在组件的构造函数中声明对服务的依赖,框架会在恰当的时候根据构造函数依赖将服务注入到组件中,而开发者无需关心这些依赖是如何被实例化的。一个简单的依赖注入的例子如下:

@Component({
selector: 'app-document',
templateUrl: './document.component.html',
styleUrls: ['./document.component.scss'],
providers: [PermissionService]
})
export class DocumentComponent implements OnInit { constructor(private permissionService: PermissionService) { } ngOnInit() {
} }

@Component装饰器中的providers是关键,框架会给DocumentComponent组件创建一个注入器对象并新建PermissionService的一个实例将该实例存储到注入器中。组件在需要PermissionService时,会根据TypeScript的类型匹配从注入器中拿到该类型对象的实例,无须在重复显式的实例化。需要注意的是,服务的每一次注入(也就是使用providers声明),该服务都会被创建出新的实例,组件的所有子组件均默认继承父组件的注入器对象,复用该注入器里存储的服务实例。这种机制可保证服务以单例模式运行,除非某个子组件再次注入(即通过providers声明)。这种灵活的方式让你有更多的选择来定义一个服务的生命周期:

①当一个服务可以全局单例的方式运行时,可以将服务注册到根组件或者如下代码所示这样:

@Injectable({
providedIn: 'root'
})
export class AccountService {
constructor(){}
}

@Injectable()装饰器中的providedIn的值是”root“,这样,这个服务就可以全局单例的方式运行了。

②当一个服务需要在不同的组件上存储不同的数据和逻辑时,这意味着你不需要也不能在多个组件中共享这个组件,那你就在每个组件的@Component()装饰器中使用providers属性来注入这个服务,那这个服务就会在这个组件和这个组件的子组件上面形成单例(子组件会共享父组件的依赖注入器),而不会在别的不相干的组件上共享该服务了。框架会根据@Component()装饰器的providers属性来为组件初始化一个注入器。

路由Router

angular作为一个单页应用(SPA)框架,路由是必不可少的组成部分之一。在Angular中,路由的作用是建立URL路径和组件之间的对应关系,根据不同的URL路径匹配出相应的组件并渲染。假设通讯录应用需要添加一个通话记录页面,简单的路由配置如下:

注意该配置的第一项path的值为空,这表示默认路由。上述配置的作用如下:·

访问http://www.abc.com/时,页面渲染ContactListComponent组件。·

访问http://www.abc.com/record时,页面渲染RecordListComponent组件。

组件树的节点会不断发生变化,如下图所示:

原来的组件树中多了一个路由指令(标签名为<router-outlet></router-outlet>),上图的模板语法为:

路由指令router-outlet起着类似于“插座”的作用,根据当前的URL路径匹配插入对应的组件节点,实现了主体内容(页面)的刷新,这就是Angular路由最基本的功能。路由指令还支持多重嵌套,实现子路由功能。假设通讯录应用的通话记录页面需要新增标签页切换功能,用来切换显示全部来电及未接来电,可以修改路由配置如下:

上面配置中的record条目新增了一个children的配置项,用于设置子路由的信息。当URL改变为http://www.abc.com/record时,显示的是AllRecordsComponent组件视图,通话记录组件子路由功能如下图所示。

路由还支持路径参数,如http://www.abc.com/list/123,其中123为联系人ID,从而实现类似于RESTful风格的URL形式。另外,在同一层节点上还可以放置多个路由指令,实现从属路由功能。关于更多、更强大的路由功能将会在后面的章节中详细介绍。

应用模块Module

在上面章节中,读者了解了Angular应用中的六个主要组成部分,那么这些不同的组成部分是如何组织起来,构成一个完整的功能单元甚至是完整的应用呢?Angular引入了模块机制,对某些特定的功能特性进行封装,可能包含若干组件、指令、服务等,甚至拥有独立的路由配置,其关系如下图所示。

每个angular应用中最少会有一个模块,这个模块就是根模块(默认名称是AppModule)。除了这个根模块外,你可以根据自身的业务特点将不同的组件、服务、路由和指令等等都封装到一个模块中,一般不建议将应用的所有逻辑都封装到根模块中。在angular中,除了根模块外,其他的模块类型有:封装某个完整功能的特性模块(FeatureModule)、封装一些公共构件的共享模块(SharedModule),以及存放应用级别核心构件的核心模块(CoreModule)。

  • 路由:特性模块也可以自带路由配置,当特性模块导入到根模块后,特性模块的路由配置会自动与根模块里的路由配置合并。·
  • 组件和指令:在默认情况下,模块内的组件和指令是私有的。也就是说,特性模块A被导入到根模块后,根模块依然不能使用特性模块A里的组件和指令,除非特性模块A里显式暴露了某些组件或指令,这些暴露的组件或指令相当于模块的API。
  • 服务:服务的处理则有些特殊,通过依赖注入机制,服务同样可以注入到模块里,但跟组件里的依赖注入的作用域并不相同。注入到组件里的服务只能使用在该组件及其子组件上,而注入到模块里的服务在整个应用里均能使用,因为所有模块都共享着同一个应用级别的根注入器。这种机制似乎有点违背模块的封装性,到底这种设计是否合适,当有命名冲突时又是怎么解决的,这些疑问会在后续章节一一解答。

Angular已经封装了不少常用的特性模块,如:

  • ApplicationModule——封装一些与启动相关的工具。
  • CommonModule——封装一些常用的内置指令和内置管道等。
  • BrowserModule——封装在浏览器平台运行时的一些工具库,同时将Common-Module和ApplicationModule打包导出,所以通常在使用时引入BrowserModule就可以了。
  • FormsModule和ReactiveFormsModule——封装与表单相关的组件指令等。
  • RouterModule——封装与路由相关的组件指令等。
  • HttpModule——封装与网络请求相关的服务等。

上述已提及,Angular通过引导运行根模块来启动应用,引导方式有两种:动态引导和静态引导。要理解两者的区别,先来看看Angular应用的启动过程——Angular应用在运行前,都需要经过编译器对模块、组件等进行编译,编译完成后才开始启动应用并渲染界面。

动态引导和静态引导的区别就在于编译的时机不同,动态引导是将所有代码加载到浏览器后,在浏览器中进行编译,即JiT编译;而静态引导是将编译过程前置到开发时的工程打包阶段,加载到浏览器的将是编译后的代码,称为AoT编译。

假设根模块为AppModule,动态引导的代码如下:

动态引导从platformBrowserDynamic函数启动,该函数是从'@angular/platform-browser-dynamic'文件模块(关于Angular文件模块将在下一节中讲述)导入的。动态引导启动的模块AppModule即是我们编写的模块。再来看看静态引导的示例代码:

静态引导从platformBrowser函数启动,这个函数是从@angular/platform-browser文件模块导入的,跟动态引导的不是同一个。静态引导启动的是AppModuleNgFactory模块,这是AppModule经过编译处理后生成的模块(app.module文件编译后生成app.module.ngfactory文件)。由于省去了浏览器编译这个步骤,因此应用启动的速度也会更快。

AngularCLI包含的构建工具已经非常好地支持AoT编译,在开发中只需要按照JiT方式进行代码编写,构建时打开AoT编译选项,如ngbuild--aot,即可完成如文件编译及静态引导等这些AoT处理过程。JiT编译开发流程简单明了,但性能欠佳,仅适合在开发阶段使用;而AoT编译性能提升明显,推荐使用。

构建工具在不断迭代升级中,在未来的迭代版本中将会支持在开发阶段实施AoT编译。

源码结构介绍

Angular是基于TypeScript编写的,TypeScript又是ES6的超集,而ES6给开发者带来的一个新特性是文件级别的模块功能。利用这个特性,整个Angular项目的源码是基于ES6模块来组织的。注意这里的模块和上一节提到的模块并不是同一个概念,上节提到的是应用级别的模块,是以功能特性为划分依据的;而本节的模块是语言级别的,是以物理文件或文件夹为划分依据的。GitHub源码结构如下图所示(angular/packages)。

在实际的npm包使用中,使用@angular作为命名空间来引入Angular模块,如@angular/common。源码在发布至npm前会经过构建,把packages文件夹下的模块打包成@angular前缀。

在上图中,用方框标记的为常用的一级模块(一级文件夹),下面对主要的模块进行介绍。

·packages/core:存放核心代码,如变化监测机制、依赖注入机制、渲染等,核心功能的实现、装饰器(@Component、@Directive等)也会存放到这个模块中。

·packages/common:存放一些常用的内置指令和内置管道等。

·packages/forms:存放与表单相关的内置组件及内置指令等。

·packages/http:存放与网络请求相关的服务等。在Angular4.3以上版本中已有网络请求工具的替代方案,存放在packages/common/http里,在后面的章节会有详细的解释。

·packages/router:存放与路由相关的组件和指令等。

·packages/platform-<x>:存放的是与引导启动相关的工具。Angular支持在多个平台下运行,不同的平台都有对应的启动工具,这些启动工具会被封装到不同的模块里,如服务端渲染这个场景的启动工具存放在packages/platform-server下,浏览器的启动工具则存放在packages/platform-browser下。

这些语言级别的模块和应用级别的模块非常相似,实际上它们是有关联的,如CommonModule模块本身就存放在packages/common里,当开发者需要引用packages/common里的诸多指令或者组件时,只需引入CommonModule即可。CommonModule的作用是打包packages/common下零散的组件指令并作为该模块的API暴露出来,方便开发者一次性引入。关于其他模块,有兴趣的读者可以到Angular的GitHub库中查阅。

angular复习笔记2-架构总览的更多相关文章

  1. Angular复习笔记7-路由(下)

    Angular复习笔记7-路由(下) 这是angular路由的第二篇,也是最后一篇.继续上一章的内容 路由跳转 Web应用中的页面跳转,指的是应用响应某个事件,从一个页面跳转到另一个页面的行为.对于使 ...

  2. Angular复习笔记7-路由(上)

    Angular复习笔记7-路由(上) 关于Angular路由的部分将分为上下两篇来介绍.这是第一篇. 概述 路由所要解决的核心问题是通过建立URL和页面的对应关系,使得不同的页面可以用不同的URL来表 ...

  3. Angular复习笔记6-依赖注入

    Angular复习笔记6-依赖注入 依赖注入(DependencyInjection)是Angular实现重要功能的一种设计模式.一个大型应用的开发通常会涉及很多组件和服务,这些组件和服务之间有着错综 ...

  4. Angular复习笔记5-指令

    Angular复习笔记5-指令 在Angular中,指令是一个重要的概念,它作用在特定的DOM元素上,可以扩展这个元素的功能,为元素增加新的行为.本质上,组件可以被理解为一种带有视图的指令.组件继承自 ...

  5. angular复习笔记4-模板

    Angular复习笔记4-模板 简介 模板是一种自定义的标准化页面,通过模板和模板中的数据结合,可以生成各种各样的网页.在Angular中,模板的默认语言是HTML,几乎所有的HTML语法在模板中都是 ...

  6. angular复习笔记1-开篇

    前言 学习和使用angular已经有一段时间了.这段时间利用angular做了一个系统,算是对angular有了一个全面的认识,趁着现在有一些时间,把angular的一些知识记录一下. 安装angul ...

  7. angular复习笔记3-组件

    组件Component 组件是构成angular应用的核心,angular的有序运行依赖于组件的协同工作,组件之于angular应用就像是汽车和汽车零部件的意思. 概述 近几年的前端发展迅速,各种工程 ...

  8. angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构

    ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入 ...

  9. MySQL学习笔记-MySQL体系结构总览

    MySQL体系结构总览 不管是用哪种数据库,了解数据库的体系结构都是极为重要的.MySQL体系结构主要由数据库和数据库实例构成. 数据库:物理操作系统文件或者其它文件的集合,在mysql中,数据库文件 ...

随机推荐

  1. 如何新建WebAPI,生成注释,TestAPI的项目

    一.新建WebAPI的项目 1. 在Web下,ASP.NET Web 应用程序,点击确定 2. 点击确定 3. 如图所示, 新建Controller 4 . 运行项目 二.注释 1. 在生成中,勾选x ...

  2. js之切换全屏和退出全屏实现

    应用场景:比如很多网页游戏全屏之类的,或者是网上看小说等. 核心代码: //控制全屏 function enterfullscreen() { //进入全屏 $("#fullscreen&q ...

  3. golang字节数组拷贝BlockCopy函数实现

    在C#中,Buffer.BlockCopy(Array, Int32, Array, Int32, Int32) 函数使用比较广泛,其含义: 将指定数目的字节从起始于特定偏移量的源数组复制到起始于特定 ...

  4. 剑指offer:把数组排成最小的数

    题目描述: 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323. 思路分析: ...

  5. [转]OpenTK学习笔记(1)-源码、官网地址

    OpenTK源码下载地址:https://github.com/opentk/opentk OpenTK使用Nuget安装命令:OpenTK:Install-Package OpenTK -Versi ...

  6. Proj.4 升级新版本5.x和6.x

    目录 Proj.4 升级新版本5.x和6.x 0.缘起 1.5.x和6.x更新情况简述 PROJ 5.x 更新 PROJ 6.x 更新 2.从PROJ.4向新版本迁移 迁移到5.x版本 迁移到6.x版 ...

  7. Android TextView部分文字实现点击事件

    This is the class for text whose content and markup can both be changed. (这是一个内容和标记都可以更改的文本类) 快速实现 直 ...

  8. 【MySQL】Mysql模糊查询like提速优化

    一般情况下like模糊查询的写法为(field已建立索引): SELECT `column` FROM `table` WHERE `field` like '%keyword%'; 上面的语句用ex ...

  9. t6_sumdoc

    C:\Users\Administrator\Documents\sumdoc 2019\sumdoc t6 final\sumdoc t615C:\Users\Administrator\Docum ...

  10. Logstash配置以服务方式运行

    Logstash官网最新版下载地址以及YUM源:https://www.elastic.co/cn/downloads/logstash Logstash最常见的运行方式即命令行运行 ./bin/lo ...