angular复习笔记2-架构总览
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-架构总览的更多相关文章
- Angular复习笔记7-路由(下)
Angular复习笔记7-路由(下) 这是angular路由的第二篇,也是最后一篇.继续上一章的内容 路由跳转 Web应用中的页面跳转,指的是应用响应某个事件,从一个页面跳转到另一个页面的行为.对于使 ...
- Angular复习笔记7-路由(上)
Angular复习笔记7-路由(上) 关于Angular路由的部分将分为上下两篇来介绍.这是第一篇. 概述 路由所要解决的核心问题是通过建立URL和页面的对应关系,使得不同的页面可以用不同的URL来表 ...
- Angular复习笔记6-依赖注入
Angular复习笔记6-依赖注入 依赖注入(DependencyInjection)是Angular实现重要功能的一种设计模式.一个大型应用的开发通常会涉及很多组件和服务,这些组件和服务之间有着错综 ...
- Angular复习笔记5-指令
Angular复习笔记5-指令 在Angular中,指令是一个重要的概念,它作用在特定的DOM元素上,可以扩展这个元素的功能,为元素增加新的行为.本质上,组件可以被理解为一种带有视图的指令.组件继承自 ...
- angular复习笔记4-模板
Angular复习笔记4-模板 简介 模板是一种自定义的标准化页面,通过模板和模板中的数据结合,可以生成各种各样的网页.在Angular中,模板的默认语言是HTML,几乎所有的HTML语法在模板中都是 ...
- angular复习笔记1-开篇
前言 学习和使用angular已经有一段时间了.这段时间利用angular做了一个系统,算是对angular有了一个全面的认识,趁着现在有一些时间,把angular的一些知识记录一下. 安装angul ...
- angular复习笔记3-组件
组件Component 组件是构成angular应用的核心,angular的有序运行依赖于组件的协同工作,组件之于angular应用就像是汽车和汽车零部件的意思. 概述 近几年的前端发展迅速,各种工程 ...
- angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构
ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入 ...
- MySQL学习笔记-MySQL体系结构总览
MySQL体系结构总览 不管是用哪种数据库,了解数据库的体系结构都是极为重要的.MySQL体系结构主要由数据库和数据库实例构成. 数据库:物理操作系统文件或者其它文件的集合,在mysql中,数据库文件 ...
随机推荐
- Mybatis27题
1.什么是Mybatis? Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动.创建连接.创建statement等繁杂 ...
- kafka(二) 高性能技术分析
参考文章: http://www.infoq.com/cn/articles/kafka-analysis-part-6 Partition提供并行处理的能力 Kafka是一个Pub-Sub的消息系统 ...
- [Beta阶段]测试报告
[Beta阶段]测试报告 博客目录 测试方法及过程 在正式发布前,为检验后端各接口功能的正确性,后端服务器对压力的耐受程度,以及前端各页面.功能的运行情况,我们对我们的服务器及小程序进行了多种测试.除 ...
- 记一次netty http server给客户端返回reset包排除
类似文章:解决用netty去做web服务时,post长度过大的问题 现象:当客户端给server发送的请求体较大时,服务直接给客户端返回reset包. tcpdump: 应用还没有完全收上去,就clo ...
- uniapp - 富文本编辑器editor(仅支持App和微信小程序)
uniapp - editor富文本编辑器用法示例 丢几个图,用心看下去(-.-) 这里使用了https://ext.dcloud.net.cn/plugin?id=412 插件,用于选择字体颜色.其 ...
- 【重庆师范大学】PHP博客训练-Thinkphp
设计数据库 CREATE TABLE `user` ( `user_id` int unsigned NOT NULL AUTO_INCREMENT, `username` varchar() COM ...
- APISIX系列 | 使用 docker-apisix 安装APISIX服务
官方仓库:https://github.com/iresty/docker-apisix 官方仓库 拉取 git clone git@github.com:iresty/docker-apisix.g ...
- Netty 4 实现一个 NettyClient
本文章为作者原创,有问题的地方请提出指正. 1.类继承Diagram 2.定义EndPoint类 目前仅仅定义了2个方法,分别用来获取本地或远程服务器的地址. package netty; impor ...
- Visionworks OpenVX
[TOC] Visionworks OpenVX OpenVX heterogeneous computation framework Spec OpenVX 1.2源碼解析 - 目錄結構 除了官方的 ...
- SVN 从主干合并到分支库
主干库:平时开发用的库, 分支库:中途需要进行上生产环境的库 分支库的版本从主干库拉过去就行 红色的为分支库. 创建的速度很快. 1.创建好后,在主干库添加一个文件. 2.然后分支库进行合并,这里用e ...