截止到目前为止,Angular2.0完成了其alpha-32版本的开发,新的版本还在迭代开发当中,这其中有个问题,就是每个版本相比于以前的版本都会有一些改动,包括API方面的修改,这会导致很多基于以前版本写的demo都无法运行,而且其官网上给出的教程也是基于之前的版本而没有及时更改,这就造成了即使按照官网的教程去编写运行也可能得到不预期的结果,针对这种情况,本文采用最新的较稳定的Angular alpha-31版本重构其官网上的step by step guide,并以此为基点,介绍Angular2中的组件(components).
组件(Components)是在软件开发过程中的一种发展趋势,它基于封装技术,着力于减少软件模块开发中的耦合度,在传统的后端语言开发中早已经得到了很好的实现和发展,比如微软的com组件。在web开发中,截止到目前为止,还没有一种能够得到浏览器原生支持并且广泛使用的组件技术。目前W3C制定出了web components规范,Shadow DOM作为实现该规范的一种关键技术也是在不断的发展中。可惜的是目前浏览器支持度并不好,离实际广泛使用还存在很长的一段距离。所以在目前比较著名的解决方案是谷歌的polymer和Mozilla的X-TAG,它们允许在一部分现代浏览器上能够封装自定义用户组件,并且让该组件独立于客户端js之外,即不能够通过客户端js去修改或者操作该组件内部元素,自定义组件对外表现为一个整体。这就好比html5的video 标签,你只需要在页面上放置一个video标签,它就能够被浏览器渲染成一个播放的画面以及播放控制条,即使你在开发者工具中查看原码,也能够看到一个video标签而不能够看到它内部的实现。外部js只能够通过有限的API去控制它的行为,而且不管该页面本来样式有何不同,它在不同页面上渲染出来的效果都是相同的。这就是web 组件化。
Angular2.0也正是按照这一思路来设计整个框架的,可以说Angular2.0中,组件是它的核心概念。前面讲到,由于浏览器目前还不能够提供对web Components的原生支持,所以Angular从框架的角度来视图解决这一问题。本文的目的就是讲解如何在Angular2.0中实现一个组件。
为了便于快速演示效果,免去环境搭建的过程,我在github上传了一个种子项目,用于以后对Angular2.0的学习。本文基于这个项目,一步步去了解组件的概念。
步骤一,克隆种子项目到本地
在本地创建一个文件夹,在该文件夹中进行git clone操作
git clone https://github.com/myzhibie/ng2-demo.git
第二步,本地全局安装gulp(如果已经安装,可以省去这一步)
npm install -g gulp
第三步,在项目根目录下安装项目的所有依赖项
npm install
这一步可能需要FQ,如果失败一般都是网络原因。
如果上述步骤都成功,那么到此为止,已经可以运行该种子项目了
gulp play
在原始的种子项目中,src文件夹下已经有了本文的所有代码作为参考,可以将它们完全(除了index.html和style.css)删除然后自己创建。
1.创建一个简单的组件
在src文件夹下创建一个display.js文件(严格来讲是ts文件,因为本文是采用typescript脚本来构架的,当然你也可以使用ES5,ES6来编写整个项目,但是相比于typescript较为复杂,Angular2官方的教程中,也是采用首选typescript脚本,目测随着Angular2的推动,微软的typescript会火啊),用来编写组件的逻辑,并在html中采用如下代码引用它
System.import('display');
在index.html中,添加一行html代码
<display></display>
这表示自定义了一个display组件。
然后转到display.js中,编写逻辑如下
1 import {ComponentAnnotation as Component, ViewAnnotation as View, bootstrap,NgFor,NgIf} from 'angular2/angular2'; 2 3 class FriendService { 4 names:Array<string>; 5 constructor(){ 6 this.names = ["Alice", "Aarav", "Martín", "Shannon", "Ariana", "Kai"]; 7 } 8 } 9 @Component({ 10 selector: 'display', 11 hostInjector:[ FriendService ] 12 }) 13 @View({ 14 templateUrl: 'showData.html', 15 directives:[NgFor,NgIf] 16 }) 17 class DisplayComponent { 18 myName: string; 19 names:Array<string>; 20 constructor(friendService:FriendService) { 21 this.myName='myzhibie'; 22 this.names=friendService.names; 23 } 24 updateItems(name:string){ 25 this.names.push(name); 26 } 27 doneTyping($event){ 28 if($event.which===13){ 29 this.updateItems($event.target.value); 30 $event.target.value=null; 31 } 32 } 33 } 34 35 bootstrap(DisplayComponent);
代码第一行首先引入了component,view,bootstrap,以及ngFor,ngIf几个核心的指令,第3到8行定义了一个外部的service,类似于Angular1.X中的service,可以将一些公用的方法或者代码抽象为一个service,然后采用注入的方式在Component中进行调用,从思想上来讲,这和Angular1.X没有任何区别,但是具体的定义方式发生了较大变化,Angular2中就是使用class关键字来定义,结构很清晰。这个service拥有一个names属性是字符串数组类型,并在构造函数中对它进行了初始化。代码9到12行定义了该组件相关的配置项,selector属性是一个选择器,用于选择该组件对应于html页面上的哪个DOM元素。hostInjector属性表示将FriendService注入到了这个组件当中,这行代码是保证能够在Angular2 alpha31版本运行的依赖注入的方式,官网上给出的是appInjector,那个是之前的版本的DI的配置属性。
代码13-16行定义了该组件view相关的配置,templateUrl代表其对应的模板文件,是showData.html.directives表示在其模板中可以使用的内置指令,这里我们选择ngFor和ngIf,前者用来循环,后者用来进行判断,具体用法在showData.html中会给出。
代码第17到33行就是定义了一个完整的组件,你会发现就是一个class,而不是Angular1.X中那种controller的形式,实际上,这个组件的class就是它的Controller,这种定义方式更优雅简洁一些。20-23行是这个组件的构造函数,用来初始化它的属性,其中names属性是通过依赖注入的friendService对象来的。同时在该controller种定义了两个方法,updateItems用来添加新的names项,doneTyping方法用来处理组件中的input标签在完成输入后按回车键去添加names项的操作。下面是showData.html的内容
1 <p>friends:</p> 2 <ul> 3 <li *ng-for="#name of names"> 4 {{ name }} 5 </li> 6 </ul> 7 <input #nametext (keyup)="doneTyping($event)"> 8 <button (click)="updateItems(nametext.value)" >addItems</button> 9 <p class="p2" *ng-if="names.length > 3">you have many friends</p>
第3到6行是一个ul里面加入了li,通过ng-for指令循环(类似于1中的ng-repeat)其controller中的names属性,注意调用指令的时候前面要加上*表示这是一个Angular2的内置指令,同时使用#来定义在循环中的一个局部变量,第4行使用{{}}来绑定变量。第7行是一个input 标签,该标签中定义了一个nametext局部变量,可以在该页面的其他地方引用。需要注意的是,你不能定义成nameText或者name-text,这两种方式在Angular2中会被转化,得不到确定的结果,相信这个问题在随后的正式发布中会得到解决。同时,给input标签注册一个onkeyup事件,让它指向其controller中的doneTyping方法,并且它的参数$event代表event对象。第8行给button标签注册一个onclick事件,它的参数通过局部变量nametext来引用上面input标签的值。第9行在p标签中采用ng-if指定,如果其controller的names长度大于3就显示这个p标签,否则不显示。
运行以上代码,结果如下:
可以通过在input中写入值点击按钮添加li,也可以通过回车键添加。
2.组件之间的引用
通过上面的练习,我们知道了如何定义一个组件并为它绑定事件来响应用户请求,实际上就是定义一个完整的组件,但是web引用并不是一个组件就能够搞定的,Angular2.0允许你以更简单的方式在自定义组件中引用其他组件,也就是组件之间的引用。
首先的index.html中添加一行代码
<parent></parent>
表示这是一个parent组件,然后添加一个parent.js文件,编写逻辑如下:
1 import {ComponentAnnotation as Component, ViewAnnotation as View, bootstrap,NgFor,NgIf} from 'angular2/angular2'; 2 3 @Component({ 4 selector:'parent' 5 }) 6 7 @View({ 8 template:'<h1 class="hparent">{{message}}</h1><child></child>', 9 directives:[ChildComponent] 10 }) 11 class ParentComponent { 12 message:string; 13 constructor(){ 14 this.message='parent component'; 15 } 16 } 17 18 @Component ({ 19 selector:'child' 20 }) 21 22 @View({ 23 template:'<p class="pchild">{{ message }}</p>' 24 }) 25 26 class ChildComponent{ 27 message:string; 28 constructor(){ 29 this.message='child component'; 30 } 31 } 32 bootstrap(ParentComponent);
代码第3到16行用于定义父组件,第18到31行定义了一个子组件ChildComponent,需要注意的是第8-9行,组件之间的引用是在view注解中template中插入组件的占位符<child></child>,然后在directives中引用它的class名称。这就完成了组件的调用,通过这种方式,可以采用“堆积木”的方法来构建我们的web应用,同时它提高了代码的复用率。运行结果如下:
审查元素结果如下:
从上面可以看出,Angular2.0对于组件的定义和使用相对于Angular1.x是更为简单和优雅的,学习曲线较Angular1.X平坦一些。
原码地址https://github.com/myzhibie/ng2-demo