angular2架构详解
http://www.tuicool.com/articles/EvEZjmZ
本文将从框架角度简单介绍Angular2的基本架构,如果你对Angular2还不太了解,请先阅读另两篇文章Angular2入门教程1 和 Angular2入门教程-2 实现TodoList App ,然后再阅读此文。
Angular2是一个完整的单页应用开发框架。很多人拿它跟React比,相比来说,React是一个基础框架,更像是一个库,你需要很多第三方的库才能方便的开发一个完整的应用。而Angular2则提供了很多组件,或者叫库,比如Directive(指令)、组件框架、模板、依赖注入、绑定、路由等,在这些库的帮助下,你更多的只需要关注具体业务的开发。
当你编写一个Angular2的应用的时,你从一个Component开始,编写这个Component需要展示的页面模板,并在Component中控制模板上显示的内容和用户的交互。一般还需要编写一个service来实现业务逻辑。你还需要针对这个模板编写一个样式。最后,你需要把这个Component和service定义到一个module(模块)里。
这样就完成了这个应用模块的开发。剩下的就是用Angular提供的方法去启动应用的 root module
,也就是根模块。如果你的应用有几个模块需要交互或跳转,就需要定义路由。
所以,你只需要开发业务相关的Component组件,以及组件之间的数据交互和页面跳转,剩下的像页面文件的渲染、变量的双向绑定、用户事件的响应、页面的跳转和参数传递等等,都由框架来完成。
在这篇文章里,我们就来看看Angular2框架背后提供的一些功能,了解了这些,有助于我们理解一个Angular应用是怎么工作的,从而帮助我们更好的利用框架,开发出更高质量的应用。
Angular2框架,提供了以下几个基本的功能:
- 模块
- 组件
- 服务
- 模板
- 数据绑定
- 依赖注入
模块是一个业务功能的集合,我们可以把几个组件、服务和其他一些业务模型的定义都加到一个模块里,他的功能更多的是帮助我们更好的组织我们的代码,方便代码重用。模板、数据绑定、依赖注入是定义一套这个框架的规则和语法,我们用这套规则和语法编写的代码,就能够享受Angular2给我们带来的便利。
模块
Angular2的模块可以将组件、服务、指令、方法等封装成一个模块,如下图所示(图片摘自官网):
比如我们开发一个系统,里面包含一个”我的消息”的功能,这个功能包含一些组件,如消息列表、详细详情、回复、新消息和好友列表等。除了这些组件,我们又需要相应的服务来跟服务器交互来提供数据。我们可能也需要一些环境变量等。我们可以把这些组件、服务等都封装在一个方法里面,像这样:
import{ NgModule }from'@angular/core';
import{ CommonModule }from'@angular/common';
import{ FormsModule }from'@angular/forms'; import{ FriendModule }from'./friend/friend.module'; import{ MessageListComponent }from'./message/list.component';
import{ MessageDetailComponent }from'./message/detail.component';
import{ MessageFormComponent }from'./message/form.component';
import{ MessageService }from'./todo.service'; @NgModule({
imports: [CommonModule, FormsModule, FriendModule ],
exports: [MessageListComponent],
declarations: [MessageListComponent, MessageDetailComponent, MessageFormComponent],
providers: [MessageService]
})
exportclassMessageModule{}
在上面的模块定义中,我们又引入了一个 FriendModule
,因为好友的功能在另一个模块里,我们不需要重新实现,而只是引入他既可以。我们也可以只引入好友模块里面的某一个组件,也可以只引入服务。
在MessageModule中,在 exports: [MessageListComponent]
中导出了MessageListComponent,这样,别的模块可以使用这个组件,来显示消息列表。
在应用的根模块中,除了上面的这些’imports’,’providers’这些定义以外,还有一个 bootstrap: [AppComponent]
,表示这个应用一开始会加载这个组件到index页面中。
在Angular2中,提供了很多模块,例如上面的 FormsModule
、 CommonModule
, 还有Router等。他们都是一个个模块,也算是库,可以单独引入使用,也可以只引入需要的部分。
组件和指令
Angular2的组件是一个可重用的单元,包含模版、样式,还有数据、事件等交互逻辑。
下面之前文章中TodoList应用中的一个组件:
import{ Component }from'@angular/core'; import{ Todo }from'../todo';
import{ TodoService }from'../todo.service'; @Component({
selector: 'todo-list',
templateUrl: 'app/todo/list/list.component.html',
styleUrls: ['app/todo//list/list.component.css']
})
exportclassTodoListComponent{
newTodo: Todo = newTodo();
constructor(private todoService: TodoService) { }
addTodo() {
this.todoService.addTodo(this.newTodo);
this.newTodo =newTodo();
}
get todos() {
returnthis.todoService.getAllTodos();
}
}
Angular2的组件通过一个@Component的Decorator(装饰器)定义一个组件TodoListComponent,组件中有2个方法,一个用于初始化任务列表数据,一个用于相应页面上的新建任务的事件。
在@Component中,定义了这个组件使用的模板、样式,和在它的父组件中所在的位置,也就是html标签 <todo-list>
。
在Angular2里,Directive(指令)跟组件类似,工作原理也跟上面类似。它跟组件一样,也是定义一种可重用的结构,添加用户交互。实际上,在Angular2中,Component继承自Directive接口,并提供了模板相关的属性和功能。
虽然组件和指令区别不大,但是,因为它在Angular2里面非常重要,而且是我们开发应用的基本单位,所以它被独立出来,并被放在Angular2框架的核心位置。
有关组件,需要说明的是,在一个Angular2的应用中,组件是一个属性的结构,就好像html的DOM树一样,每个Angular2应用都有一个根组件,然后它会有一个个的子组件。在我们之前的一篇文章《Angular2入门教程-2》中我们在设计系统的时候,就是从设计组件开始,得到的是一个组件树:
每个组件(除了根组件)都会有一个父组件,每个组件定义中的 selector
的值,对应父组件中的一个html标签。
元数据
元数据就是在定义模块、组件、服务的时候,Decorator方法里面的参数内容,例如一个AppComponent的元数据,就是 @Component
里面的参数,如下:
{
selector: 'root-app',
templateUrl: 'app/app.component.html',
styleUrls: ['app/app.component.css']
}
在Angular2中,Decorator被大量使用,当我们定义模板、组件、服务、指令时,都是使用Decorator来定义。顾名思义,Decorator(装饰器)就是在一个类上面添加一些额外的属性或方法。
举个例子,上面的根组件AppComponent,在定义它的时候,通过 @Component
才能把它定义成一个Angular的组件。然后我们在这个元数据里面,设置了这个组件对应的selector,模板和样式。这样Angular框架在解析这个类的时候,就会按照组件的规则去解析并初始化。当在一个页面里面遇到这个selector设置的标签(如这个例子中的 )时,就会初始化这个组件,渲染模板生成html显示到对应的标签里面,并应用样式。
服务
在Angular2中,服务是一个很宽泛的定义,任何的类都可以被定义成服务,这个类中可以包含一些业务方法,可以包含环境配置变量。Angular2也没有对服务的定义做任何的规则限制。下面就是一个最简单的服务:
exportclassSomeService{
someConfig: {foo: 'bar'}
getConfig() { returnsomeConfig; }
handle(msg: any) { console.log(msg); }
}
我们只需要定义一个class,并把它export就可以。但是,一般我们都是结合依赖注入来使用服务。
依赖注入
从Angular1的版本开始,依赖注入就是一个很核心的概念,在版本2中,主要是用于管理service实例的注入。在上面讲的service中,我们创建了一个SomeService,在传统的用法中,我们都是在需要用他的地方手动创建一个这个类的实例,然后调用他的相应方法或属性,例如:
letmyService =newSomeService();
myService.handle('the message');
但是,当我们的系统中有很多service类,甚至这些service类相互之间又需要引用的时候,我们就没有办法都通过手动创建的方式来获取service实例。更重要的是,这对于系统的解耦非常不便,不同的服务之间直接创建和引用,会让系统变得非常难以维护。
Angular给我们提供了一个非常好的解决方案,它借用了Java等语言中某些容器库的概念,将所有service实例的创建都由容器来完成,当一个service需要引用另一个service的时候,不是自己创建另一个service的实例,而是从容器中获取那个service的实例。
要使用依赖注入的功能,首先我们的service必须由一个装饰器 @Injectable
来定义:
@Injectable()
exportclassSomeService{
// 跟之前一样,省略...
}
然后,在Component中需要加一个providers,也就是服务的创建者:
@Component({
selector: 'some-list',
templateUrl: 'some.component.html',
providers: [ SomeService ]
})
exportclassSomeComponent{
constructor(private theService: SomeService) { }
// 省略其他方法。
},
这样这个服务就可以在SomeComponent中自动注入了。它的构造函数中有一个参数theService,类型是SomeService,Angular在创建这个Component的时候,就会从容器里面查找SomeService类的实例,如果有就用这个实例去初始化SomeComponent对象;如果没有就先新建一个,再初始化。这个过程,就是Angular的依赖注入。
有关依赖注入,需要注意的一点就是依赖注入的作用范围。Angular2的依赖注入是一个树形的结构,就好像组件树一样。在上面的例子中,我们在 SomeComponent
的providers
里面设置了 SomeService
,也就是说,在SomeComponent这个节点,以及它所有的子节点的组件上,SomeService类的实例是共用的,它们都共享一个实例。但是,在这个SomeComponent的父组件里,它如果也想注入SomeService来使用的话,就没有办法从容器中获得,除非在它的父组件中的providers中也添加了这个服务。
在我们之前教程《Angular2入门教程-2》的TodoList应用实例中,我们把todo应用相关的类都添加到一个模块里,内容如下:
import{ NgModule }from'@angular/core';
import{ CommonModule }from'@angular/common';
import{ FormsModule }from'@angular/forms'; import{ TodoListComponent }from'./list/list.component';
import{ TodoDetailComponent }from'./detail/detail.component';
import{ TodoItemComponent }from'./item/item.component';
import{ TodoService }from'./todo.service'; @NgModule({
imports: [CommonModule, FormsModule ],
declarations: [TodoListComponent, TodoDetailComponent, TodoItemComponent],
providers: [TodoService]
})
exportclassTodoModule{}
我们在这个模块的定义中定义了 providers: [TodoService]
,这样,这个模块的几个Component都可以共用这个TodoService的实例。
数据绑定
在jQuery或者更早的时代,当我们需要更新页面的内容的时候,我们一般都需要自己获得页面的DOM,然后,设置他的值。当页面上的内容需要更新到js端的时候,又需要设置一些事件,如onclick, onblur等,然后在响应事件里面再从页面获得这个值。这不仅需要些很多代码、浪费时间,还非常容易出错。终于,Angular把我们从这些枯燥的工作中解放出来,提供了数据绑定的功能。
在Angular1.x的版本中,数据绑定是通过轮询实现的。在Angular1里,所有需要绑定的数据都会在$scope中,Angular1.x有一个轮训机制,每隔一段时间就检查所有绑定的变量,检查他们现在的值跟上次检查的值是否一致,如果不一致,就触发相应的方法更新页面的内容。这虽然给我们开发带来了便利,但是如果有太多的变量需要监听,就会造成很大的性能问题。
在Angular2里面,绑定的数据的监听是通过zone.js实现。通俗来讲,zone给所有有可能更新数据的方法加了一个补丁,就像AOP,或者说代理模式。每当这些更新数据的方法被调用的时候,就会触发另一个方法,告诉Angular有数据修改,Angular再去判断变量是否修改,如果有修改,就更新DOM。
而且,Angular2的数据更新检测是在每个组件上有一个检测器。这样,就算应用中有再多绑定的变量,当有一个数据修改的事件以后,也只是对应的那个组件的检测器被触发,来检查它以及它所有的子组件的数据修改。
这两方面结合,就使得Angular2应用的性能能够有很大的提升。
说了那么多原理,我们再来看看Angular2是数据绑定的几种方式,结合下面的代码看看每种方式的用途。这个模板中包括一个输入框用于新建,包含一个列表显示,以及一个子组件:
<headerclass="header">
<inputclass="new-hero"placeholder="输入名字"[(ngModel)]="newHero.name"(keyup.enter)="addHero()">
</header>
<ul>
<li>{{hero.name}}</li>
<hero-detail[hero]="selectedHero"></hero-detail>
<li(click)="selectHero(hero)"></li>
</ul>
我们看看这个模板里面包含的4种数据绑定的方式:
- 插值表达式
这种方式是将组件中的数据hero.name显示到页面上那个 - 标签里。
[user] 属性绑定
这种方式用于将当前组件中的变量传递到子组件,也就是从list组件中,对于每一个hero,将它传递到子组件HeroDetailComponent中。在子组件中,就需要这样来获取:
@Input() todo: Todo;
(click),(keyup.enter)
这就是事件绑定,将页面上的DOM事件绑定到组件中的某个方法上。也就是当用户点击(click),或者敲回车键后弹起(keyup.enter)时,调用组件中的某个方法。
- [(ngModel)]
这种是双向的数据绑定,上面3个都是单向的。双向绑定就是用户在页面上修改这个输入框的值时,这个值就会直接反馈到组件中,同样,如果在组件中通过某种方式修改了这个值,页面上的输入框中,也会显示最新的值。
对于上面的 []
和 ()
两种类型的绑定,可以理解成’输入’和’输出’。 []
相当于一个组件的输入,一般这个输入从它的父组件获得;()
相当于组件的输出。上面的例子是用事件绑定,将数据”输出”到组件里。实际上,我们还可以用一种 EventEmitter
把数据”输出”到父组件。
下面的图直观的描述了上面4种数据绑定方式的作用方式:(图片来自官网,其中’[property] = “value”‘这个表述的似乎不太正确???):
它们之间的关系
为了描述这几个功能之间的关系,先看看下面这张图(图片摘自官网):
这张图就比较清楚的说明了组件、服务、模板、Directive(指令)、数据绑定和依赖注入的相互关系。结合这张图和上面说的开发一个Angular2的应用的基本过程,这样就更容易理解了。
下面,我们来看一下Angular应用的工作流程。
- 我们定义一个根模块和一个根组件,然后在main.ts里面用这个根模块启动应用。
- 根组件里面的一个html标签如果匹配了某一个组件的’selector’,这个组件就会被加载在这个标签里面。这样,整个应用就是一个组件树,
- 我们定义的Component信息,也就是类和Metadata(元数据),Angular会根据这个组件定义将组件中定义的模板显示到selector对应的标签上,将样式应用到模板页面上。组件和模板之间又通过数据绑定联系起来,组件中的变量通过数据绑定展示到模板上,模板又通过事件绑定,对应到组件里的方法上。Directive的工作原理也跟上面类似。
- 最后,我们定义的service被Angular的Injector保存在一个树形结构的容器里,在某个组件中当我们需要使用这个service时,就可以在构造函数中直接获得这个service的实例,而不用手动创建。这样,多个组件,或者模块都可以共用一个service的实例。所以,service除了提供业务方法,也能提供共享数据、数据传输等功能。
angular2架构详解的更多相关文章
- NopCommerce源码架构详解--初识高性能的开源商城系统cms
很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从中学习很多企业系统.软件开发的规范和一些新的技术.技巧,可以快速地提高我们 ...
- 领域驱动设计(Domain Driven Design)参考架构详解
摘要 本文将介绍领域驱动设计(Domain Driven Design)的官方参考架构,该架构分成了Interfaces.Applications和Domain三层以及包含各类基础设施的Infrast ...
- WeChatAPI 开源系统架构详解
WeChatAPI 开源系统架构详解 如果使用WeChatAPI,它扮演着什么样的角色? 从图中我们可以看到主要分为3个部分: 1.业务系统 2.WeChatAPI: WeChatWebAPI,主要是 ...
- hdfs文件系统架构详解
hdfs文件系统架构详解 官方hdfs分布式介绍 NameNode *Namenode负责文件系统的namespace以及客户端文件访问 *NameNode负责文件元数据操作,DataNode负责文件 ...
- 迈向angularjs2系列(2):angular2指令详解
一:angular2 helloworld! 为了简单快速的运行一个ng2的app,那么通过script引入预先编译好的angular2版本和页面的基本框架. index.html: <!DOC ...
- NopCommerce源码架构详解
NopCommerce源码架构详解--初识高性能的开源商城系统cms 很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从 ...
- RESTful 架构详解
RESTful 架构详解 分类 编程技术 1. 什么是REST REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移. 它首次 ...
- Nop--NopCommerce源码架构详解专题目录
最近在研究外国优秀的ASP.NET mvc电子商务网站系统NopCommerce源码架构.这个系统无论是代码组织结构.思想及分层都值得我们学习.对于没有一定开发经验的人要完全搞懂这个源码还是有一定的难 ...
- Zookeeper系列二:分布式架构详解、分布式技术详解、分布式事务
一.分布式架构详解 1.分布式发展历程 1.1 单点集中式 特点:App.DB.FileServer都部署在一台机器上.并且访问请求量较少 1.2 应用服务和数据服务拆分 特点:App.DB.Fi ...
随机推荐
- C# wave mp3 播放器探寻
C# wave mp3 播放器探寻 最近无聊,想听听歌曲.可怜新电脑上歌曲就两三首,要听其它的就得在旧电脑上播放.可是,那台古董但不失健壮的本本被老婆无情的霸占了.无奈. 思来想去,得,写个程序播 ...
- How-to: Use HBase Bulk Loading, and Why
How-to: Use HBase Bulk Loading, and Why http://blog.cloudera.com/blog/2013/09/how-to-use-hbase-bulk- ...
- arm ncnn
ncnn网址:https://github.com/Tencent/ncnn 1. sudo apt-get update sudo apt-get upgrade 2. 命令:sudo apt-ge ...
- ubuntu+anaconda
1.下载anaconda 查看ubuntu是32位还是64位 命令: uname -m 如果显示i686,你安装了32位操作系统 如果显示 x86_64,你安装了64位操作系统 uname -a 查看 ...
- elasticsearch查询语句
1,安装es 安装java环境 # java --versionjava version "1.8.0_65" Java(TM) SE Runtime Environment (b ...
- Web测试——功能测试
由于本人工作接触Web测试,所以我从网上找的资料,学习了解web测试哪些内容,然后自己整理汇总的随笔,如文章中有不足的地方,请大家多多指教:或者文章内容与他人相似,望见谅. 功能测试: 1.链接测试: ...
- C/S,B/S的区别与联系
C/S 是Client/Server 的缩写.服务器通常采用高性能的PC.工作站或小型机,并采用 大型数据库系统,如Oracle.Sybase.Informix 或SQL Server.客户端需要安装 ...
- js中去掉字符串的空格、回车换行
//例如下面这个json串,中间的\n表示换行 var str = "{' retmsg':'success ',\n' trans_date':' 20170906'}"; co ...
- IE的“浏览器模式”和“文档模式的区别”
1.浏览器模式 用于切换IE针对该网页的默认文档模式.对不同版本浏览器的条件备注解析.发送给网站服务器的用户代理(User_Agent)字符串的值.网站可以根据浏览器返回的不同用户代理字符串判断浏览器 ...
- 创建springboot的聚合工程(二)
前篇已经成功创建了springboot的聚合工程并成功访问,下面就要开始子工程木块之间的调用: springboot项目的特点,一个工程下面的类必须要放在启动类下面的子目录下面,否则,启动的时候会报错 ...