WhyAngular2
Angular1.x显然非常成功,那么,为什么要剧烈地转向Angular2?
性能的限制
AngularJS当初是提供给设计人员用来快速构建HTML表单的一个内部工具。随着时间的推移,各种特性被加入进去以适应不同场景下的应用开发。然而由于最初的架构限制(比如绑定和模板机制),性能的提升已经非常困难了。
快速变化的WEB
在语言方面,ECMAScript6的标准已经完成,这意味着浏览器将很快支持例如模块、类、lambda表达式、 generator等新的特性,而这些特性将显著地改变JavaScript的开发体验。
在开发模式方面,Web组件也将很快实现。然而现有的框架,包括Angular1.x对WEB组件的支持都不够好。
移动化
想想5年前......现在的计算模式已经发生了显著地变化,到处都是手机和平板。Angular1.x没有针对移动应用特别优化,并且缺少一些关键的特性,比如:缓存预编译的视图、触控支持等。
简单易用
说实话,Angular1.x太复杂了,学习曲线太陡峭了,这让人望而生畏。Angular团队希望在Angular2中将复杂性封装地更好一些,让暴露出来的概念和开发接口更简单。
要让Angular2应用跑起来不是件轻松的事,因为它用了太多还不被当前主流浏览器支持的技术。所以,我们需要一个工具链:
Angular2是面向未来的科技,要求浏览器支持ES6+,我们现在要尝试的话,需要加一些 垫片来抹平当前浏览器与ES6的差异:
初识Angular2
写一个Angular2的HelloWorld应用相当简单,分三步走:
1. 引入Angular2预定义类型
1. import{Component,View,bootstrap}from"angular2/angular2";
import是ES6的关键字,用来从模块中引入类型定义。在这里,我们从angular2模块库中引入了三个类型: Component类、View类和bootstrap函数。
2. 实现一个Angular2组件
实现一个Angular2组件也很简单,定义一个类,然后给这个类添加注解:
1. @Component({selector:"ez-app"})
2. @View({template:"Hello,Angular2
"})
3. classEzApp{}
class也是ES6的关键字,用来定义一个类。@Component和@View都是给类EzApp附加的元信息,被称为注解/Annotation。
@Component最重要的作用是通过selector属性(值为CSS选择符),指定这个组件渲染到哪个DOM对象上。 @View最重要的作用是通过template属性,指定渲染的模板。
3. 渲染组件到DOM
将组件渲染到DOM上,需要使用自举/bootstrap函数:
1. bootstrap(EzApp);
这个函数的作用就是通知Angular2框架将EzApp组件渲染到DOM树上。
简单吗?我知道你一定还有疑问,别着急,我们慢慢把缺失的知识点补上!
把@Component的selector属性改为"ez-app",还应该改哪里可以获得和示例同样的结果?
注解/Annotation
你一定好奇@Component和@View到底是怎么回事。看起来像其他语言(比如python)的装饰器,是这样吗?
ES6规范里没有装饰器。这其实利用了traceur的一个实验特性:注解。给一个类加注解,等同于设置这个类的annotations属性:
1. //注解写法
2. @Component({selector:"ez-app"})
3. classEzApp{...}
等同于:
1. classEzApp{...}
2. EzApp.annotations =[newComponent({selector:"ez-app"})];
很显然,注解可以看做编译器(traceur)层面的语法糖,但和python的装饰器不同, 注解在编译时仅仅被放在annotation里,编译器并不进行解释展开- 这个解释的工作是 Angular2完成的:
据称,注解的功能就是Angular2团队向traceur团队提出的,这不是traceur的默认选项,因此你看到,我们配置systemjs在使用traceur模块时打开注解:
1. System.config({
2. map:{traceur:"lib/traceur"},
3. traceurOptions:{annotations:true}
4. });
修改示例代码中的EzApp组件,不用注解实现同样的功能。
如果你了解一点Angular1.x的bootstrap,可能隐约会感受到Angular2中bootstrap的一些变化 - 我指的并非代码形式上的变化。
以组件为核心
在Angular1.x中,bootstrap是围绕DOM元素展开的,无论你使用ng-app还是手动执行bootstrap() 函数,自举过程是建立在DOM之上的。
而在Angular2中,bootstrap是围绕组件开始的,你定义一个组件,然后启动它。如果没有一个组件,你甚至都没有办法使用Angular2!
支持多种渲染引擎
以组件而非DOM为核心,意味着Angular2在内核隔离了对DOM的依赖 - DOM仅仅作为一种可选的渲染引擎存在:
上面的图中,DOM Render已经实现,Server Render正在测试,iOS Render和AndroidRender 是可预料的特性,虽然我们看不到时间表。
这有点像React了。
最简单的模板
组件的View注解用来声明组件的外观,它最重要的属性就是template - 模板。 Angular2的模板是兼容HTML语法的,这意味着你可以使用任何标准的HTML标签编写组件模板。
所以,在最简单的情况下,一个Angular2组件的模板由标准的HTML元素构成,看起来就是一段HTML码流。Angular2将原封不同地渲染这段模板:
有两种方法为组件指定渲染模板:
1. 内联模板
可以使用组件的View注解中的template属性直接指定内联模板:
1. @View({
2. template:`hello
3.
4. })
在ES6中,使用一对`符号就可以定义多行字符串,这使得编写内联的模板轻松多了。
2. 外部模板
也可以将模板写入一个单独的文件:
1.
2. hello
3.
然后在定义组件时,使用templateUrl引用外部模板:
1. @View({
2. templateUrl :"ezcomp-tpl.html"
3. })
在示例的模板中,增加一个输入文本框和一个按钮!
示例文件1.
import {Component,View,bootstrap} from"angular2/angular2";
@Component({selector : "ez-app"})
@View({
template : `
使用ES6开发Angular2应用的一个好处就是,可以不用拼接模板字符串了。
`
})
class EzApp{}
bootstrap(EzApp);
directives- 使用组件
在Angular2中,一个组件的模板内除了可以使用标准的HTML元素,也可以使用自定义的组件!
这是相当重要的特性,意味着Angular2将无偏差地对待标准的HTML元素和你自己定义的组件。这样,你可以建立自己的领域建模语言了,这使得渲染模板和视图模型的对齐更加容易,也使得模板的语义性更强:
声明要在模板中使用的组件
不过,在使用自定义组件之前,必需在组件的ViewAnnotation中通过directives属性声明这个组件:
1. @View({
2. directives :[EzComp],
3. template:"
4. })
你应该注意到了,directives属性的值是一个数组,这意味着,你需要在这里声明所有你需要在模板中使用的自定义组件。
修改示例代码:
1. 增加一个EzLogo组件
2. 在EzCard组件的模板中使用这个组件
示例文件2
import {Component,View,bootstrap} from"angular2/angular2";
@Component({selector:"ez-app"})
@View({
directives:[EzCard],
template:`
})
class EzApp{}
@Component({selector : "ez-card"})
@View({
template : `
})
class EzCard{}
bootstrap(EzApp);
div.ez-app{background:red;padding:10px;}
div.ez-card{background:green;padding:10px;}
在模板中使用可以{{表达式}}的方式绑定组件模型中的表达式,当表达式变化时, Angular2将自动更新对应的DOM对象:
上图的示例中,模板声明了h1的内容将绑定到组件实例的title变量。Angular2 框架将实时检测title的变化,并在其变化时自动更新DOM树中h1的内容。
修改模板,将“新闻来源”字段及内容移动到文章尾部
示例3
=================================================。
import{Component,View,bootstrap} from "angular2/angular2";
@Component({selector:"ez-app"})
@View({
template:`
{{date}} 来源:{{source}} {{content}} {{title}}
`
})
class EzApp{
constructor(){
this.title= "证监会:对恶意做空是有监测的";
this.date = "2015年07月11日15:32:35";
this.source = "北京晚报";
this.content = `
证监会新闻发言人邓舸昨天表示,近期,证监会要求所有上市公司制定维护股价稳定的方案,举措包括大股东增持、董监高增持、公司回购、员工持股计划、股权激励等,相关方案应尽快公布,并通过交易所平台向投资者介绍生产经营管理情况、加强投资者关系管理、维护股价稳定、做好投资者沟通工作。他介绍,该措施得到了上市公司大股东的积极响应,包括北京创业板董事长俱乐部、创业板首批28家公司实际控制人、浙江24家公司董事长等多个上市公司联盟以及大连、青岛、湖南等多地上市公司集体发声,宣布通过积极增持、回购、暂不减持等方式稳定公司股价。截至9日,两市已有655家公司公布股份增持、回购计划,积极维护公司股价稳定。
`;
}
}
bootstrap(EzApp);
[property]- 绑定属性
在模板中,也可以使用一对中括号将HTML元素或组件的属性绑定到组件模型的某个表达式,当表达式的值变化时,对应的DOM对象将自动得到更新:
等价的,你也可以使用bind-前缀进行属性绑定:
1. @View({template:`
很容易理解,通过属性,应用相关的数据流入组件,并影响组件的外观与行为。
需要注意的是,属性的值总是被当做调用者模型中的表达式进行绑定,当表达式变化时,被调用的组件自动得到更新。如果希望将属性绑定到一个常量字符串,别忘了给字符串加引号,或者,去掉中括号:
1. //错误,Angular2将找不到表达式 Hello,Angular2
2. @View({template:``})
3. //正确,Angular2识别出常量字符串表达式 'Hello,Angular2'
4. @View({template:``})
5. //正确,Angular2识别出常量字符串作为属性textContent的值
6. @View({template:`
修改示例代码,使EzApp组件的标题颜色每秒钟随机变化一次!
import{bind,Component,View,bootstrap} from "angular2/angular2";
@Component({selector:"ez-app"})
@View({
template:`Hello,Angular2
`
})
class EzApp{
constructor(){
this.color= "red";
}
}
bootstrap(EzApp);
(event)- 监听事件
在模板中为元素添加事件监听很简单,使用一对小括号包裹事件名称,并绑定到表达式即可:
上面的代码实例为DOM对象h1的click事件添加监听函数onClick()。
另一种等效的书写方法是在事件名称前加on-前缀:
1. @View({template:`
修改示例代码,点击EzApp组件的h1标题时,自动变换名称!
import{Component,View,bootstrap} from "angular2/angular2";
@Component({selector:"ez-app"})
@View({
template:`
Yourturn! {{sb}}
`
})
class EzApp{
constructor(){
this.names=["Jason","Mary","Linda","Lincoln","Albert","Jimmy"];
this.roulette();
}
//轮盘赌
roulette(){
varidx = parseInt(Math.random()*this.names.length);
this.sb = this.names[idx];
}
}
bootstrap(EzApp);
button{width:100%;padding:10px;}b{color:red}
================================
#var - 局部变量
有时模板中的不同元素间可能需要互相调用,Angular2提供一种简单的语法将元素映射为局部变量:添加一个以#或var-开始的属性,后续的部分表示变量名,这个变量对应元素的实例。
在下面的代码示例中,我们为元素h1定义了一个局部变量v_h1,这个变量指向该元素对应的DOM对象,你可以在模板中的其他地方调用其方法和属性:
1. @View({
2. template:`
3. hello
4.
5. `
6. })
如果在一个组件元素上定义局部变量,那么其对应的对象为组件的实例:
1. @View({
2. directives:[EzCalc],
3. template:"
4. })
在上面的示例中,模板内的局部变量c指向EzCalc的实例。
为示例代码的变色按钮添加事件监听,点击该按钮时,将EzApp组件的h1标题变为黑色背景,白色字体!
import {Component,View,bootstrap} from"angular2/angular2";
@Component({selector:"ez-app"})
@View({
template:`
I choose
WHO?
`
})
class EzApp{}
bootstrap(EzApp);
b{color:red}
使用条件逻辑
有时我们需要模板的一部分内容在满足一定条件时才显示,比如右边示例中的EzReader组件,对于试用用户,它将在正文之上额外显示一个广告:
这是指令NgIf发挥作用的场景,它评估属性ngIf的值是否为真,来决定是否渲染 template元素的内容:
1. @View({
2. template:`
3.
4.
5.
6.
7. ...
` })
Angular2同时提供了两种语法糖,让NgIf写起来更简单,下面的两种书写方法和上面的正式语法是等效的:
1. //使用template attribute
2. <img src="ad.jpg"template="ng-iftiral==true">
3. //使用*前缀
4. <img src="ad.jpg"*ng-if="tiral==true">
看起来,显然*ng-if的书写方法更加有人情味儿,不过无论采用哪种书写方法,都将转换成上面的正式写法,再进行编译。
需要指出的是,NgIf是Angular2预置的指令/Directive,所以在使用之前,需要:
修改示例代码中EzApp组件的模板,将trial属性设置为false,运行看有什么不同。
//引入NgIf类型
import {Component,View,bootstrap,NgIf}from "angular2/angular2";
@Component({selector:"ez-app"})
@View({
directives:[EzReader],
template:`
`
})
class EzApp{}
@Component({
selector :"ez-reader",
properties:["trial"]
})
@View({
directives:[NgIf],
template : `
{{content}}`
})
class EzReader{
constructor(){
var self = this;
this._trial = true;
this.banner ="img/banner.jpg";
this.content = `
“没关系,我已经没有放射性了。”史强对坐在旁边的汪森说,“这两天,我让人家像洗面口袋似的翻出来洗了个遍。
这次会议本来没安排你参加,是我坚决要求请你来的,嘿。我保准咱哥俩这次准能出风头的。”
史强说着,从会议桌上的烟灰缸中拣出一只雪茄屁股,点上后抽一口,点点头,心旷神怡地把烟徐徐吐到对面与会
者的面前,其中就有这支雪茄的原主人斯坦顿,一名美国海军陆战队上校,他向大史投去鄙夷的目光。
这次与会的有更多的外国军人,而且都穿上了军装。在人类历史上,全世界的武装力量第一次面对共同的敌人。
常伟思将军说:“同志们,这次与会的所有人,对目前形势都有了基本的了解,用大史的话说,信息对等了。人类
与外星侵略者的战争已经开始,虽然在四个半世纪后,我们的子孙才会真正面对来自异星的三体人侵者,我们现在与之
作战的仍是人类;但从本质上讲,这些人类的背叛者也可以看成来自地球文明之外的敌人,我们是第一次面对这样的敌人。
下一步的作战目标十分明确,就是要夺取‘审判日’号上被截留的三体信息,这些信息,可能对人类文明的存亡具有重要
意义。
`;
}
}
bootstrap(EzApp);
img{width:100%;transition:height2s;}
使用分支逻辑
如果组件的模板需要根据某个表达式的不同取值展示不同的片段,可以使用NgSwitch系列指令来动态切分模板。比如右边示例中的广告组件EzPromotion,需要根据来访者性别的不同推送不同的广告:
NgSwitch包含一组指令,用来构造包含多分支的模板:
NgSwitch
NgSwitch指令可以应用在任何HTML元素上,它评估元素的ngSwitch属性值,并根据这个值决定应用哪些template的内容(可以同时显示多个分支):
1.
NgSwitchWhen
NgSwitchWhen指令必须应用在NgSwitch指令的子template元素上,它通过属性ngSwitchWhen指定一个表达式,如果该表达式与父节点的NgSwitch指令指定的表达式值一致,那么显示这个template的内容:
1.
2.
3. [ng-switch-when]="variable">...
4.
5. ng-switch-when="constant">...
6.
NgSwitchDefault
NgSwitchDefault指令必须应用在NgSwitch指令的子template元素上,当没有NgSwitchWhen指令匹配时,NgSwitch将显示这个template的内容:
1.
2. ng-switch-default>...
3.
需要注意的是,NgSwitch系列指令都是Angualr2的预置指令,在模板中使用之前,需要
思考下,NgSwitch和NgIf的应用场景有什么区别?
//引入NgSwitch类型
import{Component,View,bootstrap,NgSwitch,NgSwitchWhen,NgSwitchDefault} from"angular2/angular2";
@Component({selector:"ez-app"})
@View({
directives:[EzPromotion],
template:`
`
})
class EzApp{}
@Component({
selector :"ez-promotion",
properties:["gender"]
})
@View({
directives:[NgSwitch,NgSwitchWhen,NgSwitchDefault],
template : `
`
})
class EzPromotion{}
bootstrap(EzApp);
img{width:100%;}
NgFor- 循环逻辑
如果希望利用一组可遍历的数据动态构造模板,那么应当使用NgFor指令。例如右边示例中的EzStar组件,用来展示演员的作品列表:
迭代
NgFor指令应用在template元素上,对ngForOf属性指定的数据集中的每一项实例化一个template的内容:
1. ng-for [ng-for-of]="items">
2. ----------
3.
如果items数据集有3条记录,那么会生成3个li对象,就像这样:
1. ----------
2. ----------
3. ----------
不过这没多大用。
使用数据项
好在我们还可以为数据集的每一项声明一个局部变量,以便在模板内引用:
1. ng-for [ng-for-of]="items" #item>
2. {{item}}
3.
假如items数据集是一个数组:["China","India","Russia"],那么现在生成的结果就有点用了:
1. China
2. India
3. Russia
使用数据项索引
有时还需要数据项在数据集中的索引,我们也可以为数据集的每一项的索引声明一个局部变量,以便在模板内引用:
1. ng-for [ng-for-of]="items" #item #i="index">
2. [{{i+1}}]{{item}}
3.
现在生成的结果更规矩了:
1. [1] China
2. [2] India
3. [3] Russia
语法糖
与NgIf类似,Angular2也为NgFor提供了两种语法糖:
1. //使用template attribute
2. <ANY template="ng-for #item of items;#i=index">...ANY>
3. //使用*前缀
4. <ANY *ng-for="#item ofitems;#i=index">...ANY>
毫无疑问,应当尽量使用*ng-for的简便写法,这可以提高模板的可读性。
修改示例代码,在每一部影片名称之前,显示其序号!
//引入NgSwitch类型
import {Component,View,bootstrap,NgFor}from "angular2/angular2";
@Component({selector:"ez-app"})
@View({
directives:[EzStar],
template:`
`
})
class EzApp{}
@Component({
selector :"ez-star"
})
@View({
directives:[NgFor],
template : `
`
})
class EzStar{
constructor(){
this.actor = "JasonStatham";
this.films = [
"Mechanic: Rescurrection / 2016",
"Spy / 2015",
"Furious 7/2015",
"Wild Card /2015",
"The Expendables /2014",
"Home Front / 2013",
"Hummingbird /2013",
"Fast & Furious 6/ 2013",
"Parker / 2013"
];
}
}
bootstrap(EzApp);
div{background:url(img/jason.jpg);}
h2{background:#26a69a;padding:5px;margin:0px;color:white}
ul{margin:0px;padding:0px;list-style:none;font-family:Courier;}
li{line-height:30px;border-bottom:1pxsolid #ccc;color:#0f0;}
styles -设置模板样式
组件既然处于UI层,就应当好看些,好看是构造良好用户体验的一部分。Angular2的组件模板基于HTML,那么显然,我们需要通过样式表/CSS来调整组件的外观。
和模板类似,我们有两种方法为组件设置CSS样式:
1. 内联样式
可以使用组件View注解的styles属性来设置内联样式:
1. @View({
2. styles:[`
3. h1{background:#4dba6c;color:#fff}
4. `]
5. })
2. 外部样式
也可以把样式定义在单独的文件中:
1. /*ez-greeting.css*/
2. h1{background:#4dba6c;color:#fff}
然后使用View注解的styleUrls属性来引入外部样式:
1. @View({
2. styleUrls:["ez-greeting.css"]
3. })
将示例组件的标题栏设置为文本居中!
import {Component,View,bootstrap} from"angular2/angular2";
@Component({selector : "ez-app"})
@View({
styles:[`
div.ez-greeting{font-family:Courier;background:#ede7f6;box-shadow:02px 5px 0;}
h1{background:#4dba6c;color:#fff}
div.content{padding:10px;}
`],
template : `
使用ES6开发Angular2应用的一个好处就是,可以不用拼接样式字符串了。
`
})
class EzApp{}
bootstrap(EzApp);
Angular2采用ShadowDom作为组件的渲染基础,这意味着组件被渲染到独立的 Shadow Tree上,这很好,可以实现DOM对象和样式的良好封装:
但问题是,除了Chrome之外的大多数的浏览器目前还不支持ShadowDom,因此,Angular2 提供了三种将模板渲染到DOM的策略。
以下面的模板为例,我们看三种策略下的渲染结果差异:
1. @View{
2. template:"hello
",
3. styles:["h1{color:red}"]
4. }
全局仿真策略/EmulatedUnscopedShadowDomStrategy
采用这个策略时,Angular2将模板直接插入DOM树,并将模板的样式原封不动地插入head头部。这意味着不同组件之间的样式可能冲突 : 在右边的示例代码中,你可以清楚的看到,EzApp组件的h1样式污染了其他的 h1元素,所有的h1都变成红色了。
示例代码渲染后的DOM如下:
这个策略不需要浏览器原生支持ShadowDom,是当前版本(alpha.28)的默认策略。
作用域仿真策略/EmulatedScopedShadowDomStrategy
采用这个策略时,Angular2将模板直接插入DOM树,对模板的样式重新定义CSS选择符后插入head头部。由于样式进行了重命名,所以不同组件之间的样式不会冲突。
示例代码在这个策略下的渲染结果是:
这个策略也不需要浏览器原生支持ShadowDom。
原生策略/NativeShadowDomStrategy
采用这个策略时,Angular2将在组件的宿主DOM对象上建立一个ShadowDom树,这颗树与主DOM树是隔离的,所以,这是实现Web组件的理想方案:
如果浏览器原生支持ShadowDom,那么应当使用这个策略。
import {bind,Component,View,bootstrap}from "angular2/angular2";
@Component({selector:"ez-app"})
@View({
template:"
styles:["h1{color:red}"]
})
class EzApp{}
bootstrap(EzApp);
设置ShadowDom策略
在Angular2中,ShadowDom的三种策略对应于三个类,这三个类继承自ShadowDomStrategy:
Angular2的内核引用的是父类ShadowDomStrategy,我们只需要从三个继承类中选择一个与之绑定就可以实现不同策略的选择。下面的示例意味着选择NativeShadowDomStrategy:
1. bind(ShadowDomStrategy).toFactory(function(doc){
2. returnnewNativeShadowDomStrategy(doc.head);
3. },[DOCUMENT_TOKEN])
上面的表达式用来提交给Angular2的注入器/DI,可以理解为:如果注入器需要实例化一个ShadowDomStrategy 实例,应当以DOCUMENT_TOKEN为参数,调用一个匿名的工厂函数,而这个工厂函数将返回一个 NativeShadowDomStrategy类的实例。
ES6支持lambda表达式,因此我们可以写的简单一些:
1. bind(ShadowDomStrategy).toFactory(doc =>newNativeShadowDomStrategy(doc.head),[DOCUMENT_TOKEN])
补丁包
在Angular2的alpha.28版本的官方发行包中,没有包含默认ShadowDom策略之外的另两种策略实现模块,因此我们单独打了一个包render.dev.js。
如果你采用的是Chrome浏览器,将示例代码中的ShadowDom策略改为NativeShadowDomStrategy!
import {bind,Component,View,bootstrap}from "angular2/angular2";
import {ShadowDomStrategy} from'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {EmulatedUnscopedShadowDomStrategy} from'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
import {EmulatedScopedShadowDomStrategy} from'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
import {NativeShadowDomStrategy} from'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
@Component({selector:"ez-app"})
@View({
template:"
styles:["h1{color:red}"]
})
class EzApp{}
varinjectables = [
bind(ShadowDomStrategy)
.toFactory((doc)=> new EmulatedScopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN])
];
bootstrap(EzApp,injectables);
属性声明 - 暴露成员变量
属性是组件暴露给外部世界的调用接口,调用者通过设置不同的属性值来定制 组件的行为与外观:
在Angular2中为组件增加属性接口非常简单,只需要在Component注解的 properties属性中声明组件的成员变量就可以了:
1. //EzCard
2. @Component({
3. properties:["name","country"]
4. })
上面的代码将组件的成员变量name和country暴露为同名属性,这意味着在EzApp 的模板中,可以直接使用中括号语法来设置EzCard对象的属性:
1. //EzApp
2. @View({
3. directives :[EzCard],
4. template:"
5. })
提醒:如果要在模板中使用自定义的指令(组件是一种指令),必须在View注解的directives 属性中提前声明!
修改示例代码中EzApp组件的模板,为EzCard调用添加name和country属性!
import {Component,View,bootstrap} from "angular2/angular2";
//根组件 - EzApp
@Component({selector:"ez-app"})
@View({
directives:[EzCard],
template:`
})
class EzApp{}
//具有属性接口的组件 - EzCard
@Component({
selector:"ez-card",
properties:["name","country"]
})
@View({
template : `
My name is{{name}},
I am from{{country}}.
})
class EzCard{
constructor(){
this.name ="Mike";
this.country ="Sweden";
}
}
//渲染组件
bootstrap(EzApp);
div.ez-app{background:#ccc;padding:5px;}
div.ez-card{background:#00695c;color:white;border-radius:5px;padding:10px;min-height:100px;font-family:Courier;}
div.ez-cardb{color:#ff8900;}
与属性相反,事件从组件的内部流出,用来通知外部世界发生了一些事情:
在Angular2中为组件增加事件接口也非常简单:定义一个事件源/EventEmitter,然后通过Component注解的events接口包括出来:
1. //EzCard
2. @Component({
3. events:["change"]
4. })
5. classEzCard{
6. constructor(){
7. this.change =newEventEmitter();
8. }
9. }
上面的代码将组件EzCard的事件源change暴露为同名事件,这意味着在调用者 EzApp组件的模板中,可以直接使用小括号语法挂接事件监听函数:
1. //EzApp
2. @View({
3. template:"onChange()"> "
4. })
每次EzCard触发change事件时,EzApp的onChange()方法都将被调用。
import {Component,View,bootstrap,EventEmitter} from"angular2/angular2";
//根组件 - EzApp
@Component({selector:"ez-app"})
@View({
directives:[EzCard],
template:`
{{evtStr}}
})
class EzApp{
constructor(){
this.evtStr
}
onChange(evt){
console.log("sth.occured");
this.evtStr =JSON.stringify(evt,null,"\t");
}
}
//具有事件接口的组件 - EzCard
@Component({
selector:"ez-card",
events:["change"]
})
@View({
template : `
My name is{{name}},
I am from {{country}}.
})
class EzCard{
constructor(){
this.name ="Mike";
this.country ="Sweden";
this.change = newEventEmitter();
//模拟触发事件
setTimeout(()=>this.change.next({
src:"EzCard",
desc:"模拟事件"
}),1000);
}
}
//渲染组件
bootstrap(EzApp);
div.ez-app{background:#ccc;padding:5px;}
div.ez-card{background:#00695c;color:white;border-radius:5px;padding:10px;min-height:100px;font-family:Courier;}
div.ez-cardb{color:#ff8900;}
NgForm -表单指令
NgForm指令为表单元素/form建立一个控件组对象,作为控件的容器;而NgControlName指令为则为宿主input元素建立一个控件对象,并将该控件加入到NgForm 指令建立的控件组中:
局部变量
通过使用#符号,我们创建了一个引用控件组对象(注意,不是form元素!)的局部变量f。这个变量最大的作用是:它的value属性是一个简单的JSON对象,键对应于input元素的 ng-control属性,值对应于input元素的值:
声明指令依赖
NgForm指令和NgControlName指令都包含在预定义的数组变量formDirectives中,所以我们在 组件注解的directives属性中直接声明formDirectives就可以在模板中直接使用这些指令了:
1. //angular2/ts/src/forms/directives.ts
2. exportconst formDirectives = CONST_EXPR([
3. NgControlName,
4. NgControlGroup,
5.
6. NgFormControl,
7. NgModel,
8. NgFormModel,
9. NgForm,
10.
11. NgSelectOption,
12. DefaultValueAccessor,
13. CheckboxControlValueAccessor,
14. SelectControlValueAccessor,
15.
16. NgRequiredValidator
17. ]);
为示例代码中的select元素也使用NgControlName指令,并在反馈中显示所选择的搜索类别!
import {Component,View,bootstrap,NgIf}from "angular2/angular2";
//引入form指令集
import {formDirectives} from "angular2/forms";
//EzApp组件
@Component({selector:"ez-app"})
@View({
directives:[formDirectives,NgIf],
template:`
`,
styles:[`form{background:#90a4ae;padding:5px;}`]
})
class EzApp{
constructor(){
this.kw = "";
}
search(val){
this.kw = val.kw;
//假装在搜索,2秒钟返回
setTimeout(()=>this.kw="",2000);
}
}
bootstrap(EzApp);
如前所述,NgControlName指令必须作为NgForm或NgFormModel的后代使用,因为这个指令需要将创建的控件对象添加到祖先(NgForm或NgFormModel)所创建的控件组中。
NgControlName指令的选择符是[ng-control],这意味着你必须在一个HTML元素上定义ng-control属性,这个指令才会起作用。
属性:ngControl
NgControlName指令为宿主的DOM对象创建一个控件对象,并将这个对象以ngControl属性指定的名称绑定到DOM对象上:
1.
2. type="text"ng-control="user">
3. type="password"ng-control="pass">
4.
在上面的代码中,将创建两个Control对象,名称分别为user和pass。
属性/方法:ngModel
除了使用控件组获得输入值,NgControlName指令可以通过ngModel实现模型与表单的双向绑定:
1.
2. type="text"ng-control="user" [(ng-model)]="data.user">
3. type="password"ng-control="pass" [(ng-model)]="data.pass">
4. `
ngModel即是NgControlName指令的属性,也是它的事件,所以下面的两种写法是等价的:
1. type="text"ng-control="user" [(ng-model)]="data.user">
2. //等价于
3. type="text"ng-control="user" [ng-model]="data.user" (ng-model)="data.user">
import{Component,View,bootstrap,NgIf} from "angular2/angular2";
import {formDirectives} from "angular2/forms";
@Component({selector:"ez-app"})
@View({
directives:[NgIf,formDirectives],
template:`
{{decode(data)}}
`,
styles:[`
form{background:#e1f5fe;}
ul{list-style:none;padding:10px;margin:0px;}
li{line-height:30px;}
`]
})
class EzApp{
constructor(){
this.data= {
name : "whoami"
};
}
decode(val){
returnJSON.stringify(val,null,"\t");
}
}
bootstrap(EzApp);
NgControlGroup指令的选择符是[ng-control-group],如果模板中的某个元素具有这个属性, Angular2框架将自动创建一个控件组对象,并将这个对象以指定的名称与DOM对象绑定。
控件组可以嵌套,方便我们在语义上区分不同性质的输入:
和NgControlName指令一样,NgControlGroup指令也必须作为NgForm或NgFormModel的后代使用,因为这个指令需要将创建的控件组对象添加到祖先(NgForm或NgFormModel)所创建的控件组中。
在示例代码中再增加一个控件组,采集关于用户的兴趣爱好方面的信息!
import {Component,View,bootstrap,NgIf}from "angular2/angular2";
import {formDirectives} from "angular2/forms";
@Component({selector:"ez-app"})
@View({
directives:[NgIf,formDirectives],
template:`
{{decode(f.value)}}
`,
styles:[`
div{padding:5px;background:#b3e5fc;color:red;}
form{background:#e1f5fe;}
ul{list-style:none;padding:5px;margin:0px;}
li{line-height:30px;}
`]
})
class EzApp{
decode(val){
returnJSON.stringify(val,null,"\t");
}
}
bootstrap(EzApp);
NgFormControl- 绑定已有控件对象
与NgControlName指令不同,NgFormControl将已有的控件/Control对象绑定到DOM元素上。当需要对输入的值进行初始化时,可以使用NgFormControl指令。
下面的代码中,使用NgFormControl指令将DOM元素绑定到组件EzComp的成员变量movie上,我们需要在构造函数中先创建这个Control对象:
1. @View({
2. //将输入元素绑定到已经创建的控件对象上
3. template:``
4. })
5. classEzComp{
6. constructor(){
7. //创建控件对象
8. this.movie =newControl("Matrix II -Reload");
9. }
10. }
控件/Control是Angular2中对表单输入元素的抽象,我们使用其value属性,就可以获得对应的输入元素的值。
与NgControlName指令的另一个区别是,NgFormControl不需要NgForm或NgFormModel的祖先。
为示例代码增加采集用户工作单位的输入项,并在调试信息中显示!
import {Component,View,bootstrap} from"angular2/angular2";
import {Control,formDirectives} from "angular2/forms";
@Component({selector:"ez-app"})
@View({
directives:[formDirectives],
template:`
{{dump()}}
`,
styles:[`
form{background:#e1f5fe;}
ul{list-style:none;padding:10px;margin:0px;}
li{line-height:30px;}
`]
})
class EzApp{
constructor(){
//创建控件对象
this.name = newControl("Jason");
this.address = newControl("London U.K.");
this.telephone = newControl("114");
}
dump(){
//读取控件对象的值
var val = {
name : this.name.value,
address :this.address.value,
telephone :this.telephone.value
}
returnJSON.stringify(val,null,"\t");
}
}
bootstrap(EzApp);
NgFormModel- 绑定已有控件组
NgFormModel指令类似于NgControlGroup指令,都是为控件提供容器。但区别在于, NgFormModel指令将已有的控件组绑定到DOM对象上:
1. @View({
2. template:`
3.
4. 5. 6. 7.
8. })
9. classEzComp{
10. constructor(){
11. //创建控件组及控件对象
12. this.controls =newControlGroup({
13. name :newControl("Jason"),
14. age :newControl("45")
15. });
16. }
17. }
NgFormModel指令可以包含NgControlGroup指令,以便将不同性质的输入分组。
将示例代码中的地址和电话输入元素,使用NgControlGroup指令放入单独的一个分组中。
import{Inject,Component,View,bootstrap} from "angular2/angular2";
import {Control,ControlGroup,formDirectives} from"angular2/forms";
@Component({selector:"ez-app"})
@View({
directives:[formDirectives],
template:`
{{dump()}}
`,
styles:[`
div{background:#e1f5fe;}
ul{list-style:none;padding:10px;margin:0px;}
li{line-height:30px;}
`]
})
class EzApp{
constructor(){
this.controls = newControlGroup({
name: new Control("Jason"),
address : newControl("London U.K."),
telephone : newControl("114")
});
}
dump(){
returnJSON.stringify(this.controls.value,null,"\t");
}
}
bootstrap(EzApp);
服务 - 封装可复用代码
在Angular2中,服务用来封装可复用的功能性代码。比如Http服务,封装了ajax 请求的细节,在不同的组件中,我们只需要调用Http服务的API接口就可以给组件增加 ajax请求的功能了:
Angular2中实现一个服务非常简单直接:定义一个类,然后,它就是服务了:
1. classEzAlgo{
2. add(a,b){return a+b;}
3. sub(a,b){return a-b;}
4. }
上面的代码定义了一个相当弱智的算法服务EzAlgo,它有两个API - add()用来计算两个数的和,sub()用来计算两个数的差 。在示例中,组件EzApp依赖于这个服务:
修改右边示例代码,实现一个减法计算器!
import {Component,View,bootstrap} from"angular2/angular2";
import {formDirectives} from "angular2/forms";
//定义一个简单的算法服务
class EzAlgo{
add(a,b) { return a+b; }
sub(a,b) { return a-b; }
}
//组件定义
@Component({
selector : "ez-app"
})
@View({
directives:[formDirectives],
template : `
`
})
class EzApp{
constructor(){
this.a = 37;
this.b = 128;
//实例化服务对象
this.algo = new EzAlgo();
}
add(){
var a = +this.a,
b = +this.b;
return this.algo.add(a,b);
}
}
bootstrap(EzApp);
*{font-size:30px;font-weight:bold;}
input{width:100px;}
注入 - appInjector
在前一节的示例代码中,组件EzAlgo直接在构造函数中实例化了一个EzAlog对象,这造成了EzApp和EzAlgo的强耦合,我们可以使用Angular2的注入器/Injector进行解耦:
注入器就像婚姻介绍所,男方在婚介所登记心仪的女性特点,约好见面地点,然后,坐等发货即可。比如上图:
EzApp组件(男方)使用Component注解的appInjector属性向Angular2框架(婚介所)声明其依赖于EzAlgo(登记心仪的女性特点),并在其构造函数的参数表中使用Inject注解声明注入点(约好见面地点),然后,剩下的事儿Angular2(婚介所)就办了:
1. @Component({
2. selector :"ez-app",
3. //声明依赖
4. appInjector :[EzAlgo]
5. })
6. @View(...)
7. classEzApp{
8. //Angular2框架负责注入对象
9. constructor(@Inject(EzAlgo) algo){
10. //已经获得EzAlgo实例了!
11. }
12. }
1. 不使用appInjector,在bootstrap函数中注入EzAlgo也是可行的,你试试看!
2. 思考一下,appInjector注入和bootstrap时注入,分别在什么情况下适用?
import{Inject,Component,View,bootstrap} from "angular2/angular2";
import {formDirectives,Control} from "angular2/forms";
//定义一个简单的算法服务类
class EzAlgo{
add(a,b) { return a+b; }
sub(a,b) { return a-b; }
}
//组件定义
@Component({
selector : "ez-app",
appInjector: [EzAlgo]
})
@View({
directives:[formDirectives],
template : `
`,
styles:[`
*{font-size:30px;font-weight:bold;}
input{width:100px;}
`]
})
class EzApp{
//注入参数声明
constructor(@Inject(EzAlgo) algo){
this.a = 37;
this.b = 128;
this.algo = algo;
}
add(){
var a = +this.a,
b = +this.b;
returnthis.algo.add(a,b);
}
}
bootstrap(EzApp);
EzAlgo相当简单,使用new或者使用Injector来获得一个实例看起来差别不大。那如果我们的EzApp组件要使用Http服务呢?
第一眼看上去,Http服务显然是一个真正有用的服务 - 因为看起来相当的复杂 - Http依赖于XHRBackend和BaseRequestOptions,而XHRBackend又依赖于BrowserXHR。
我们可以有两种方法获得一个Http的实例,以便通过它获得网络访问的功能:
1. 使用new进行实例化
如果我们使用传统的new方式创建Http实例,看起来应该是这样:
1. var xhrbe =newXHRBackend(BrowserXHR);
2. var options =newBaseRequestOptions();
3. var http =newHttp(xhrbe,options);
这没什么新奇的,就是繁琐一点。
2. 使用注入器/Injector
如果使用注入器,我们需要向Angular2框架声明这些层叠的依赖关系:
1. @Component({
2. appInjector :[
3. bind(BrowserXHR).toValue(BrowserXHR),
4. XHRBackend,
5. BaseRequestOptions,
6. Http
7. ]
8. })
bind(BrowserXHR).toValue(BrowserXHR)的意思是,如果需要注入BrowserXHR类型的变量,注入这个类本身而非其实例。
原理是这样,不过Angular2已经提供了一个定义好的变量httpInjectables,我们直接引用就可以了。
Observable
Observable是一种异步编程模式,与Promise不同,Observable等多的是从数据而非行为的角度来封装异步代码。
Http服务的get()方法返回一个Observable对象,可以把Observable对象看做一个可订阅的数据流,你通过subscribe()方法订阅以后,每当数据流中有新的数据,你都会得到通知。
import{Inject,Component,View,bootstrap,NgFor} from "angular2/angular2";
//引入Http相关预定义类型
import {Http,httpInjectables} from "angular2/http";
//EzApp组件
@Component({
selector:"ez-app",
//注入Http依赖项集合
appInjector:[httpInjectables]
})
@View({
directives:[NgFor],
template:`
`,
styles:[`
img{height:200px;width:200px;}
div{float:left;margin:10px;}
`]
})
class EzApp{
//注入Http实例对象
constructor(@Inject(Http)http){
this.band = {};
http.get("api/music.json")//GET请求
.map(rsp=>rsp.json())//将相应转化为JSON格式的数据集
.subscribe(data=>this.band=data[0]);//设置band变量为数据集的第一项
}
}
bootstrap(EzApp);
body{background:black}
一个Web应用通常需要切割为多个不同的组件进行实现,然后根据用户的交互行为(通常是点击),动态地载入不同的组件,根据用户行为选择组件的过程就是路由:
由于Angular2是面向组件的,所以,Angular2的路由其实就是组件之间的路由。
点击示例页面的video或music,体会Angular2路由的作用!
import {Inject,Component,View,bootstrap}from "angular2/angular2";
//引入路由相关类型定义
import{LocationStrategy,RouteConfig,RouterOutlet,RouterLink,Router,routerInjectables}from "angular2/router";
//EzApp组件 : 路由配置与执行
@Component({selector:"ez-app"})
@View({
directives:[RouterOutlet,RouterLink],
template : `
`
})
//路由配置
@RouteConfig([
{path:"/video",component:EzVideo,as:"video"},
{path:"/music",component:EzMusic,as:"music"}
])
class EzApp{
constructor(@Inject(LocationStrategy)ls){
ls.pushState =function(){};//simple hack for crash bug.
}
}
//EzVideo组件
@Component({selector:"ez-video"})
@View({
template : `
`
})
class EzVideo{}
//EzMusic组件
@Component({selector:"ez-music"})
@View({
template : `
})
class EzMusic{}
//注入路由功能依赖项
bootstrap(EzApp,[routerInjectables]);
ez-app{background:#e9e9e9;border-radius:5px;display:block;padding:5px;}
nav{background:#ccc;padding:5px;}
navb{cursor:pointer;}
h1{padding:10px;border-radius:5px;color:#fff}
ez-videoh1{background:#f00;}
ez-musich1{background:#0f0;}
路由 - 应用步骤
在Angular2中使用路由的功能,有三个步骤:
1. 配置路由
为组件注入Router对象并通过config()方法配置路由:
1. router.config([
2. {path:"/video", component:EzVideo},
3. {path:"/music", component:EzMusic}])
上面的代码中,配置了两条路由:
2. 设置路由出口
路由出口是组件激活的地方,使用RouterOutlet指令在组件模板中声明出口:
1. @View({
2. directives:[RouterOutlet],
3. template:`
4. })
5. classEzApp{...}
3. 执行路由
使用Router的navigate()方法可以执行指定路径的路由,在示例中,当用户点击时我们调用这个方法进行路由:
1. @View({
2. template:`
3. video |
4. music
5.
6. })
我们向navigate()方法传入的路径,就是我们通过config()方法配置的路径。这样, Router就根据这个路径,找到匹配的组件,在RouterOutlet上进行激活。
在真正开始使用路由的功能之前,我们需要做一些准备工作:
1. 引用路由包
Angular2的路由模块单独打包在router.dev.js,因此首先应该引用这个包:
1.
2. 引入路由相关的预定义类型
Angular2的路由模块名为angular2/router,我们从这里引入常用类型:
1. import{LocationStrategy,Router,RouterOutlet,routerInjectables}from"angular2/router";
3. 声明路由相关依赖类型
在启动组件时,我们需要声明路由相关的依赖类型(即变量:routerInjectables),以便根注入器可以解析对这些类型的请求:
1. bootstrap(EzApp,[routerInjectables]);
import{Inject,Component,View,bootstrap} from "angular2/angular2";
import {LocationStrategy,RouterOutlet,Router,routerInjectables} from"angular2/router";
@Component({selector:"ez-app"})
@View({
directives:[RouterOutlet],
template : `
video |
music
`
})
class EzApp{
constructor(@Inject(Router)rt,@Inject(LocationStrategy) ls){
ls.pushState = function(){};
this.router = rt;
//配置路由
this.router.config([
{path:"/video",component:EzVideo},
{path:"/music",component:EzMusic}
]);
}
go(path){
//根据给定的url,选中组件并在outlet中激活
this.router.navigate(path);
}
}
@Component({selector:"ez-video"})
@View({
template : `
`
})
class EzVideo{}
@Component({selector:"ez-music"})
@View({
template : `
`
})
class EzMusic{}
bootstrap(EzApp,[routerInjectables]);
ez-app{background:#e9e9e9;border-radius:5px;display:block;padding:5px;}
nav{background:#ccc;padding:5px;}
navb{cursor:pointer;}
h1{padding:10px;border-radius:5px;color:#fff}
ez-videoh1{background:#f00;}
ez-musich1{background:#0f0;}
RouteConfig- 路由配置注解
除了使用Router的config()方法进行路由配置,Angular2还提供了路由注解,允许我们以注解的方式为组件添加路由:
1. @RouteConfig([
2. {path:"/video", component:EzVideo},
3. {path:"/music", component:EzMusic}
4. ])
5. classEzApp{...}
RouteConfigAnnotation的构造函数参数与Router的config()方法参数一致,都是一个包含若干配置项的数组。
事实上,它的确只是一个语法糖 : Angular2在bootstrap一个组件时,会检查组件是否存在 RouteConfig注解,如果存在,则利用这个信息调用Router的config()方法进行路由配置:
不过这样写起来,感觉会流畅一些,声明式的氛围更强烈一些。
修改示例代码:
1. 增加一个组件EzSport
2. 在EzApp的路由配置注释中添加指向EzSport的路由,路径为"/sport"
3. 在EzApp的模板中添加指向路径"/sport"的路由入口
import{Inject,Component,View,bootstrap} from "angular2/angular2";
import{LocationStrategy,RouteConfig,RouterOutlet,Router,routerInjectables} from"angular2/router";
//EzApp组件
@Component({selector:"ez-app"})
@View({
directives:[RouterOutlet],
template: `
video |
music
`
})
//路由配置注解
@RouteConfig([
{path:"/video", component:EzVideo},
{path:"/music", component:EzMusic}
])
class EzApp{
//注入路由器对象:Router
constructor(@Inject(Router)rt,@Inject(LocationStrategy) ls){
ls.pushState = function(){};
this.router = rt;
}
go(path){
//根据给定的url,选中组件并在outlet中激活
this.router.navigate(path);
}
}
//EzVideo组件
@Component({selector:"ez-video"})
@View({
template : `
`
})
class EzVideo{}
//EzMusic组件
@Component({selector:"ez-music"})
@View({
template : `
`
})
class EzMusic{}
bootstrap(EzApp,[routerInjectables]);
ez-app{background:#e9e9e9;border-radius:5px;display:block;padding:5px;}
nav{background:#ccc;padding:5px;}
navb{cursor:pointer;}
h1{padding:10px;border-radius:5px;color:#fff}
ez-videoh1{background:#f00;}
ez-musich1{background:#0f0;}
RouterLink- 路由入口指令
除了使用Router的navigate()方法切换路由,Angular2还提供了一个指令用来将一个DOM对象增强为路由入口:
1. @View({
2. directives:[RouterOutlet,RouterLink]
3. template:`
4.
5.
6.
7.
8. })
RouterLink指令为宿主DOM对象添加click事件监听,在触发时调用Router的 navigate()方法进行路由。
路由项别名
需要指出的是,RouterLink并不能直接使用路由项的路径,router-link属性的值是一个路由项的别名,我们需要在路由配置时通过as属性,为路由项设定别名:
1. @RouteConfig([
2. {path:"/video", component:EzVideo,as:"video"},
3. {path:"/music", component:EzMusic,as:"music"}
4. ])
在组件EzApp的模板中增加一个button,点击这个button则切换到EzMusic组件
import{Inject,Component,View,bootstrap} from "angular2/angular2";
import {LocationStrategy,RouteConfig,RouterLink,RouterOutlet,Router,routerInjectables}from "angular2/router";
//EzApp组件
@Component({selector:"ez-app"})
@View({
directives:[RouterOutlet,RouterLink],
template : `
video |
music
`
})
@RouteConfig([
{path:"/video", component:EzVideo,as:"video"},
{path:"/music", component:EzMusic,as:"music"}
])
class EzApp{
constructor(@Inject(Router)rt,@Inject(LocationStrategy) ls){
ls.pushState = function(){};
this.router = rt;
}
}
//EzVideo组件
@Component({selector:"ez-video"})
@View({
template : `
`
})
class EzVideo{}
//EzMusic组件
@Component({selector:"ez-music"})
@View({
template : `
`
})
class EzMusic{}
bootstrap(EzApp,[routerInjectables]);
ez-app{background:#e9e9e9;border-radius:5px;display:block;padding:5px;}
nav{background:#ccc;padding:5px;}
navb{cursor:pointer;}
h1{padding:10px;border-radius:5px;color:#fff}
ez-video h1{background:#f00;}
ez-musich1{background:#0f0;}
RouteRegistry- 路由注册表
Router通过config()进行的路由配置,保存在路由注册表中;而Router通过 navigate()执行路由时,利用的就是路由注册表中的信息:
在路由注册表中,保存有三种类型的路由表:
匹配表/matchers
匹配表是最基本的路由决策表,它使用正则表达式实现路径向组件的匹配。
1. @RouteConfig([
2. {path:"/video", component:EzVideo},
3. {path:"/music", component:EzMusic}
4. ])
对应生成的匹配表以正则表达式为键值,映射到汇算后的路由项:
1. /^\/video$/g =>{path:"/video",...}
2. /^\/music$/g =>{path:"/music",...}
重定向表/redirects
如果在配置时指定了路径重定向,那么将在重定向表中生成路由项。
1. @RouteConfig([
2. {path:"/video", component:EzVideo},
3. {path:"/music", component:EzMusic},
4. {path:"/test", redirectTo:"/video"}
5. ])
对应生成的重定向表以原始路径为键,映射到目标路径:
1. /test => /video
名称表/names
如果在配置路由时,通过as属性为路由项设置了别名,那么将为每个别名建立一个路由项。我们已经知道,指令RouterLink需要使用名称表。
1. @RouteConfig([
2. {path:"/video", component:EzVideo,as:"video"},
3. {path:"/music", component:EzMusic,as:"music"}
4. ])
对应生成的名称表以别名为键值,映射到汇算后的路由项:
1. video =>{path:"/video",...},
2. music =>{path:"/music",...}
修改示例代码:
1. 在EzApp的路由配置中增加路径"/test"的路由项,该路径重定向为"/video"
2. 在EzApp的模板中增加指向路径"/test"的路由入口,请使用navigate()方法
import {Inject,Component,View,bootstrap,NgFor}from "angular2/angular2";
import{LocationStrategy,RouteRegistry,RouteConfig,RouterOutlet,RouterLink,Router,routerInjectables}from "angular2/router";
//EzApp组件
@Component({selector:"ez-app"})
@View({
directives:[RouterOutlet,RouterLink,NgFor],
template : `
video |
music
{{matcher}}
{{redirect}}
{{name}}
`
})
@RouteConfig([
{path:"/video",component:EzVideo,as:"video"},
{path:"/music",component:EzMusic,as:"music"}
])
class EzApp{
//注入路由注册表对象:RouteRegistry
constructor(@Inject(RouteRegistry)rr,@Inject(LocationStrategy) ls){
ls.pushState = function(){};
this.rr = rr;
}
//显示路由注册表内容
dump(type){
var routeRecognizer =this.rr._rules.get(EzApp);
if(type === "names"){
var names = [];
routeRecognizer.names.forEach((v,k)=>{
names.push(k+"=>"+JSON.stringify(v))
});
return names;
}else if(type ==="matchers"){
var matchers = [];
routeRecognizer.matchers.forEach((v,k)=>{
matchers.push(k+"=>"+JSON.stringify(v))
})
return matchers;
}else if(type ==="redirects"){
var redirects = [];
routeRecognizer.redirects.forEach((v,k)=>{
redirects.push(k+"=>"+JSON.stringify(v))
})
return redirects;
}
return [];
}
}
//EzVideo组件
@Component({selector:"ez-video"})
@View({template: `
class EzVideo{}
//EzMusic组件
@Component({selector:"ez-music"})
@View({template : `
class EzMusic{}
bootstrap(EzApp,[routerInjectables]);
ez-app{background:#e9e9e9;border-radius:5px;display:block;padding:5px;}
nav{background:#ccc;padding:5px;}
navb{cursor:pointer;}
h1{padding:10px;border-radius:5px;color:#fff}
ez-videoh1{background:#f00;}
ez-musich1{background:#0f0;}
footer{overflow-x:scroll}