WhyAngular2

Angular1.x显然非常成功,那么,为什么要剧烈地转向Angular2


性能的限制

AngularJS当初是提供给设计人员用来快速构建HTML表单的一个内部工具。随着时间的推移,各种特性被加入进去以适应不同场景下的应用开发。然而由于最初的架构限制(比如绑定和模板机制),性能的提升已经非常困难了。

快速变化的WEB

在语言方面,ECMAScript6的标准已经完成,这意味着浏览器将很快支持例如模块、类、lambda表达式、 generator等新的特性,而这些特性将显著地改变JavaScript的开发体验。

在开发模式方面,Web组件也将很快实现。然而现有的框架,包括Angular1.xWEB组件的支持都不够好。

移动化

想想5年前......现在的计算模式已经发生了显著地变化,到处都是手机和平板。Angular1.x没有针对移动应用特别优化,并且缺少一些关键的特性,比如:缓存预编译的视图、触控支持等。

简单易用

说实话,Angular1.x太复杂了,学习曲线太陡峭了,这让人望而生畏。Angular团队希望在Angular2中将复杂性封装地更好一些,让暴露出来的概念和开发接口更简单

ES6工具链

要让Angular2应用跑起来不是件轻松的事,因为它用了太多还不被当前主流浏览器支持的技术。所以,我们需要一个工具链

Angular2面向未来的科技,要求浏览器支持ES6+,我们现在要尝试的话,需要加一些垫片来抹平当前浏览器与ES6的差异:

  • systemjs - 通用模块加载器,支持AMD、CommonJS、ES6等各种格式的JS模块加载
  • es6-module-loader - ES6模块加载器,systemjs会自动加载这个模块
  • traceur - ES6转码器,将ES6代码转换为当前浏览器支持的ES5代码。systemjs会自动加载 这个模块。

初识Angular2

写一个Angular2HelloWorld应用相当简单,分三步走:

1. 引入Angular2预定义类型

1. import{Component,View,bootstrap}from"angular2/angular2";

importES6的关键字,用来从模块中引入类型定义。在这里,我们从angular2模块库中引入了三个类型: Component类、View类和bootstrap函数。

2. 实现一个Angular2组件

实现一个Angular2组件也很简单,定义一个类,然后给这个类添加注解

1. @Component({selector:"ez-app"})

2. @View({template:"<h1>Hello,Angular2</h1>"})

3. classEzApp{}

class也是ES6的关键字,用来定义一个类。@Component@View都是给类EzApp附加的元信息,被称为注解/Annotation

@Component最重要的作用是通过selector属性(值为CSS选择符),指定这个组件渲染到哪个DOM对象上。@View最重要的作用是通过template属性,指定渲染的模板。

3. 渲染组件到DOM

将组件渲染到DOM上,需要使用自举/bootstrap函数:

1. bootstrap(EzApp);

这个函数的作用就是通知Angular2框架将EzApp组件渲染到DOM树上。

简单吗?我知道你一定还有疑问,别着急,我们慢慢把缺失的知识点补上!

@Componentselector属性改为"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.xbootstrap,可能隐约会感受到Angular2bootstrap的一些变化 - 我指的并非代码形式上的变化。

以组件为核心

Angular1.x中,bootstrap是围绕DOM元素展开的,无论你使用ng-app还是手动执行bootstrap()函数,自举过程是建立在DOM之上的。

而在Angular2中,bootstrap是围绕组件开始的,你定义一个组件,然后启动它。如果没有一个组件,你甚至都没有办法使用Angular2

支持多种渲染引擎

组件而非DOM为核心,意味着Angular2在内核隔离了对DOM的依赖 - DOM仅仅作为一种可选的渲染引擎存在:

上面的图中,DOM Render已经实现,Server Render正在测试,iOS RenderAndroidRender是可预料的特性,虽然我们看不到时间表。

这有点像React了。

最简单的模板

组件的View注解用来声明组件的外观,它最重要的属性就是template- 模板。 Angular2的模板是兼容HTML语法的,这意味着你可以使用任何标准的HTML标签编写组件模板。

所以,在最简单的情况下,一个Angular2组件的模板由标准的HTML元素构成,看起来就是一段HTML码流。Angular2原封不同地渲染这段模板:

有两种方法为组件指定渲染模板:

1. 内联模板

可以使用组件的View注解中的template属性直接指定内联模板

1. @View({

2. template:`<h1>hello</h1>

3. <div>...</div>`

4. })

ES6中,使用一对`符号就可以定义多行字符串,这使得编写内联的模板轻松多了。

2. 外部模板

也可以将模板写入一个单独的文件:

1. <!--ezcomp-tpl.html-->

2. <h1>hello</h1>

3. <div>...</div>

然后在定义组件时,使用templateUrl引用外部模板

1. @View({

2. templateUrl :"ezcomp-tpl.html"

3. })

在示例的模板中,增加一个输入文本框和一个按钮!

示例文件1.

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>template - standard HTML</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap} from"angular2/angular2";

@Component({selector : "ez-app"})

@View({

template : `

<h1>Hello,Angular2</h1>

<p>

使用ES6开发Angular2应用的一个好处就是,可以不用拼接模板字符串了。

</p>

<ul>

<li>在模板中可以使用任何标准的HTML元素</li>

<li>如果模板定义在单独的文件里,可以使用templateUrl引入</li>

</ul>

`

})

class EzApp{}

bootstrap(EzApp);

</script>

</body>

</html>

directives- 使用组件

Angular2中,一个组件的模板内除了可以使用标准的HTML元素,也可以使用自定义的组件!

这是相当重要的特性,意味着Angular2无偏差地对待标准的HTML元素和你自己定义的组件。这样,你可以建立自己的领域建模语言了,这使得渲染模板和视图模型的对齐更加容易,也使得模板的语义性更强:

声明要在模板中使用的组件

不过,在使用自定义组件之前,必需在组件的ViewAnnotation中通过directives属性声明这个组件:

1. @View({

2. directives :[EzComp],

3. template:"<ez-comp></ez-comp>"

4. })

你应该注意到了,directives属性的值是一个数组,这意味着,你需要在这里声明所有你需要在模板中使用的自定义组件。

修改示例代码:1. 增加一个EzLogo组件2. EzCard组件的模板中使用这个组件

示例文件2

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>template - component </title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap} from"angular2/angular2";

@Component({selector:"ez-app"})

@View({

directives:[EzCard],

template:`

<divclass="ez-app">

<h1>EzApp</h1>

<ez-card></ez-card>

</div>`

})

class EzApp{}

@Component({selector : "ez-card"})

@View({

template : `

<divclass="ez-card">

<h1>EzCard</h1>

</div>`

})

class EzCard{}

bootstrap(EzApp);

</script>

<style>

div.ez-app{background:red;padding:10px;}

div.ez-card{background:green;padding:10px;}

</style>

</body>

</html>

{{model}} - 文本插值

在模板中使用可以{{表达式}}的方式绑定组件模型中的表达式,当表达式变化时, Angular2将自动更新对应的DOM对象:

上图的示例中,模板声明了h1的内容将绑定到组件实例的title变量。Angular2 框架将实时检测title的变化,并在其变化时自动更新DOM树中h1的内容。

修改模板,将新闻来源字段及内容移动到文章尾部

示例3

=================================================

<!doctypehtml>

<html>

<head>

<meta charset="utf-8">

<title>template - bindmodel</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import{Component,View,bootstrap} from "angular2/angular2";

@Component({selector:"ez-app"})

@View({

template:`

<div>

<h1>{{title}}</h1>

<div>

<span>{{date}}</span> 来源:<span>{{source}}</span>

</div>

<p>{{content}}</p>

</div>

`

})

class EzApp{

constructor(){

this.title= "证监会:对恶意做空是有监测的";

this.date = "2015071115:32:35";

this.source = "北京晚报";

this.content = `

证监会新闻发言人邓舸昨天表示,近期,证监会要求所有上市公司制定维护股价稳定的方案,举措包括大股东增持、董监高增持、公司回购、员工持股计划、股权激励等,相关方案应尽快公布,并通过交易所平台向投资者介绍生产经营管理情况、加强投资者关系管理、维护股价稳定、做好投资者沟通工作。他介绍,该措施得到了上市公司大股东的积极响应,包括北京创业板董事长俱乐部、创业板首批28家公司实际控制人、浙江24家公司董事长等多个上市公司联盟以及大连、青岛、湖南等多地上市公司集体发声,宣布通过积极增持、回购、暂不减持等方式稳定公司股价。截至9日,两市已有655家公司公布股份增持、回购计划,积极维护公司股价稳定。

`;

}

}

bootstrap(EzApp);

</script>

</body>

</html>

[property]- 绑定属性

在模板中,也可以使用一对中括号HTML元素或组件的属性绑定到组件模型的某个表达式当表达式的值变化时,对应的DOM对象将自动得到更新:

等价的,你也可以使用bind-前缀进行属性绑定:

1. @View({template:`<h1bind-text-content="title"></h1>`})

很容易理解,通过属性,应用相关的数据流入组件,并影响组件的外观与行为。

需要注意的是,属性的值总是被当做调用者模型中的表达式进行绑定,当表达式变化时,被调用的组件自动得到更新。如果希望将属性绑定到一个常量字符串,别忘了给字符串加引号,或者,去掉中括号:

1. //错误,Angular2将找不到表达式 Hello,Angular2

2. @View({template:`<h1[text-content]="Hello,Angular2"></h1>`})

3. //正确,Angular2识别出常量字符串表达式 'Hello,Angular2'

4. @View({template:`<h1 [text-content]="'Hello,Angular2'"></h1>`})

5. //正确,Angular2识别出常量字符串作为属性textContent的值

6. @View({template:`<h1text-content="Hello,Angular2"></h1>`})

修改示例代码,使EzApp组件的标题颜色每秒钟随机变化一次!

<!doctypehtml>

<html>

<head>

<meta charset="utf-8">

<title>template - bindpropery</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import{bind,Component,View,bootstrap} from "angular2/angular2";

@Component({selector:"ez-app"})

@View({

template:`<h1[style.color]="color">Hello,Angular2</h1>`

})

class EzApp{

constructor(){

this.color= "red";

}

}

bootstrap(EzApp);

</script>

</body>

</html>

(event)- 监听事件

在模板中为元素添加事件监听很简单,使用一对小括号包裹事件名称,并绑定到表达式即可:

上面的代码实例为DOM对象h1click事件添加监听函数onClick()

另一种等效的书写方法是在事件名称前加on-前缀:

1. @View({template:`<h1on-click="onClick()">HELLO</h1>`})

修改示例代码,点击EzApp组件的h1标题时,自动变换名称!

<!doctypehtml>

<html>

<head>

<meta charset="utf-8">

<title>template - bindpropery</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import{Component,View,bootstrap} from "angular2/angular2";

@Component({selector:"ez-app"})

@View({

template:`

<h1>Yourturn! <b>{{sb}}</b></h1>

<button(click)="roulette()">ROULETTE</button>

`

})

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);

</script>

<style>

button{width:100%;padding:10px;}b{color:red}

</style>

</body>

</html>

================================

#var - 局部变量

有时模板中的不同元素间可能需要互相调用,Angular2提供一种简单的语法将元素映射为局部变量:添加一个以#var-开始的属性,后续的部分表示变量名这个变量对应元素的实例。

在下面的代码示例中,我们为元素h1定义了一个局部变量v_h1,这个变量指向该元素对应的DOM对象,你可以在模板中的其他地方调用其方法和属性:

1. @View({

2. template:`

3. <h1#v_h1>hello</h1>

4. <button(click) = "#v_h1.textContent = 'HELLO'">test</button>

5. `

6. })

如果在一个组件元素上定义局部变量,那么其对应的对象为组件的实例:

1. @View({

2. directives:[EzCalc],

3. template:"<ez-calc #c></ez-calc>"

4. })

在上面的示例中,模板内的局部变量c指向EzCalc的实例。

为示例代码的变色按钮添加事件监听,点击该按钮时,将EzApp组件的h1标题变为黑色背景,白色字体!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>template - local var</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap} from"angular2/angular2";

@Component({selector:"ez-app"})

@View({

template:`

<h1>

<button>变色</button>

I choose

<b#v_who>WHO?</b>

</h1>

<button(click)="v_who.textContent = 'Jason'">Jason</button>

<button(click)="v_who.textContent = 'Mary'">Mary</button>

<button(click)="v_who.textContent = 'Linda'">Linda</button>

<button(click)="v_who.textContent = 'Lincoln'">Lincoln</button>

<button(click)="v_who.textContent = 'Jimmy'">Jimmy</button>

<button(click)="v_who.textContent = 'Albert'">Albert</button>

`

})

class EzApp{}

bootstrap(EzApp);

</script>

<style>

b{color:red}

</style>

</body>

</html>

使用条件逻辑

有时我们需要模板的一部分内容在满足一定条件时才显示,比如右边示例中的EzReader组件,对于试用用户,它将在正文之上额外显示一个广告:

这是指令NgIf发挥作用的场景,它评估属性ngIf的值是否为真,来决定是否渲染template元素的内容:

1. @View({

2. template:`<!--根据变量trial的值决定是否显示广告图片-->

3. <template[ng-if]="trial==true">

4. <imgsrc="ad.jpg">

5. </template>

6. <!--以下是正文-->

7. <pre>...

` })

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的书写方法更加有人情味儿,不过无论采用哪种书写方法,都将转换成上面的正式写法,再进行编译。

需要指出的是,NgIfAngular2预置的指令/Directive,所以在使用之前,需要:

  1. 从angular2库中引入NgIf类型定义
  2. 在组件的ViewAnnotation中通过属性directives声明对该指令的引用

修改示例代码中EzApp组件的模板,将trial属性设置为false,运行看有什么不同。

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>Interpolation</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<scripttype="text/javascript" src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

//引入NgIf类型

import {Component,View,bootstrap,NgIf}from "angular2/angular2";

@Component({selector:"ez-app"})

@View({

directives:[EzReader],

template:`

<ez-reader[trial]="true"></ez-reader>

`

})

class EzApp{}

@Component({

selector :"ez-reader",

properties:["trial"]

})

@View({

directives:[NgIf],

template : `

<img[src]="banner" *ng-if="trial==true">

<pre>{{content}}</pre>`

})

class EzReader{

constructor(){

var self = this;

this._trial = true;

this.banner ="img/banner.jpg";

this.content = `

“没关系,我已经没有放射性了。”史强对坐在旁边的汪森说,“这两天,我让人家像洗面口袋似的翻出来洗了个遍。

这次会议本来没安排你参加,是我坚决要求请你来的,嘿。我保准咱哥俩这次准能出风头的。”

史强说着,从会议桌上的烟灰缸中拣出一只雪茄屁股,点上后抽一口,点点头,心旷神怡地把烟徐徐吐到对面与会

者的面前,其中就有这支雪茄的原主人斯坦顿,一名美国海军陆战队上校,他向大史投去鄙夷的目光。

这次与会的有更多的外国军人,而且都穿上了军装。在人类历史上,全世界的武装力量第一次面对共同的敌人。

常伟思将军说:“同志们,这次与会的所有人,对目前形势都有了基本的了解,用大史的话说,信息对等了。人类

与外星侵略者的战争已经开始,虽然在四个半世纪后,我们的子孙才会真正面对来自异星的三体人侵者,我们现在与之

作战的仍是人类;但从本质上讲,这些人类的背叛者也可以看成来自地球文明之外的敌人,我们是第一次面对这样的敌人。

下一步的作战目标十分明确,就是要夺取‘审判日’号上被截留的三体信息,这些信息,可能对人类文明的存亡具有重要

意义。

`;

}

}

bootstrap(EzApp);

</script>

<style>

img{width:100%;transition:height2s;}

</style>

</body>

</html>

使用分支逻辑

如果组件的模板需要根据某个表达式的不同取值展示不同的片段,可以使用NgSwitch系列指令来动态切分模板。比如右边示例中的广告组件EzPromotion,需要根据来访者性别的不同推送不同的广告:

NgSwitch包含一组指令,用来构造包含多分支的模板:

NgSwitch

NgSwitch指令可以应用在任何HTML元素上,它评估元素的ngSwitch属性值,并根据这个值决定应用哪些template的内容(可以同时显示多个分支):

1. <ANY [ng-switch]="expression">...</ANY>

NgSwitchWhen

NgSwitchWhen指令必须应用在NgSwitch指令的子template元素上,它通过属性ngSwitchWhen指定一个表达式,如果该表达式与父节点的NgSwitch指令指定的表达式值一致,那么显示这个template的内容:

1. <ANY [ng-switch]="...">

2. <!--与变量比较-->

3. <template [ng-switch-when]="variable">...</template>

4. <!--与常量比较-->

5. <templateng-switch-when="constant">...</template>

6. </ANY>

NgSwitchDefault

NgSwitchDefault指令必须应用在NgSwitch指令的子template元素上,当没有NgSwitchWhen指令匹配时,NgSwitch将显示这个template的内容:

1. <ANY [ng-switch]="...">

2. <templateng-switch-default>...</template>

3. </ANY>

需要注意的是,NgSwitch系列指令都是Angualr2的预置指令,在模板中使用之前,需要

  1. 从Angular2库中引入NgSwitch系列指令
  2. 通过ViewAnnotation的directives属性进行声明

思考下,NgSwitchNgIf的应用场景有什么区别?

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>Interpolation</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<scripttype="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

//引入NgSwitch类型

import{Component,View,bootstrap,NgSwitch,NgSwitchWhen,NgSwitchDefault} from"angular2/angular2";

@Component({selector:"ez-app"})

@View({

directives:[EzPromotion],

template:`

<ez-promotiongender="Female"></ez-promotion>

`

})

class EzApp{}

@Component({

selector :"ez-promotion",

properties:["gender"]

})

@View({

directives:[NgSwitch,NgSwitchWhen,NgSwitchDefault],

template : `

<div[ng-switch]="gender">

<template ng-switch-when="Male">

<img src="img/male-ad.jpg">

</template>

<template ng-switch-when="Female">

<img src="img/female-ad.jpg">

</template>

<template ng-switch-default>

<h1>Learn Something, NOW!</h1>

</template>

</div>

`

})

class EzPromotion{}

bootstrap(EzApp);

</script>

<style>

img{width:100%;}

</style>

</body>

</html>

NgFor- 循环逻辑

如果希望利用一组可遍历的数据动态构造模板,那么应当使用NgFor指令。例如右边示例中的EzStar组件,用来展示演员的作品列表:

迭代

NgFor指令应用在template元素上,对ngForOf属性指定的数据集中的每一项实例化一个template的内容:

1. <templateng-for [ng-for-of]="items">

2. <li>----------</li>

3. </template>

如果items数据集有3条记录,那么会生成3li对象,就像这样:

1. <li>----------</li>

2. <li>----------</li>

3. <li>----------</li>

不过这没多大用。

使用数据项

好在我们还可以为数据集的每一项声明一个局部变量,以便在模板内引用:

1. <templateng-for [ng-for-of]="items" #item>

2. <li>{{item}}</li>

3. </template>

假如items数据集是一个数组:["China","India","Russia"],那么现在生成的结果就有点用了:

1. <li>China</li>

2. <li>India</li>

3. <li>Russia</li>

使用数据项索引

有时还需要数据项在数据集中的索引,我们也可以为数据集的每一项的索引声明一个局部变量,以便在模板内引用:

1. <templateng-for [ng-for-of]="items" #item #i="index">

2. <li>[{{i+1}}]{{item}}</li>

3. </template>

现在生成的结果更规矩了:

1. <li>[1] China</li>

2. <li>[2] India</li>

3. <li>[3] Russia</li>

语法糖

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的简便写法,这可以提高模板的可读性。

修改示例代码,在每一部影片名称之前,显示其序号!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>NgFor</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<scripttype="text/javascript" src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

//引入NgSwitch类型

import {Component,View,bootstrap,NgFor}from "angular2/angular2";

@Component({selector:"ez-app"})

@View({

directives:[EzStar],

template:`

<ez-star></ez-star>

`

})

class EzApp{}

@Component({

selector :"ez-star"

})

@View({

directives:[NgFor],

template : `

<div>

<h2>{{actor}} -Films</h2>

<ul>

<li*ng-for="#film of films">{{film}}</li>

</ul>

</div>

`

})

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);

</script>

<style>

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;}

</style>

</body>

</html>

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. })

将示例组件的标题栏设置为文本居中!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>template- styles</title>

<script type="text/javascript" src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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 : `

<divclass="ez-greeting">

<h1>Hello,Angular2</h1>

<divclass="content">

<p>

使用ES6开发Angular2应用的一个好处就是,可以不用拼接样式字符串了。

</p>

<ul>

<li>在样式中可以使用任何标准的CSS语法</li>

<li>如果样式定义在单独的文件里,可以使用styleUrls引入</li>

</ul>

</div>

</div>

`

})

class EzApp{}

bootstrap(EzApp);

</script>

</body>

</html>

ShadowDom - 封装私有样式

Angular2采用ShadowDom作为组件的渲染基础,这意味着组件被渲染到独立的Shadow Tree上,这很好,可以实现DOM对象和样式的良好封装

但问题是,除了Chrome之外的大多数的浏览器目前还不支持ShadowDom,因此,Angular2 提供了三种将模板渲染到DOM策略

以下面的模板为例,我们看三种策略下的渲染结果差异:

1.@View{
2.template:"<h1>hello</h1>",
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,那么应当使用这个策略。

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>template - shadowdom strategy</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript" src="lib/system.config.js"></script>

</head>

<body>

<h1>我是H1,我在组件外</h1>

<ez-app></ez-app>

<script type="module">

import {bind,Component,View,bootstrap}from "angular2/angular2";

@Component({selector:"ez-app"})

@View({

template:"<h1>我是H1,我在组件内</h1>",

styles:["h1{color:red}"]

})

class EzApp{}

bootstrap(EzApp);

</script>

</body>

</html>

设置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])

补丁包

Angular2alpha.28版本的官方发行包中,没有包含默认ShadowDom策略之外的另两种策略实现模块,因此我们单独打了一个包render.dev.js

如果你采用的是Chrome浏览器,将示例代码中的ShadowDom策略改为NativeShadowDomStrategy

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>template - scoped&shadowdom strategy</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/render.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<h1>我是H1,我在组件外</h1>

<ez-app></ez-app>

<script type="module">

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:"<h1>我是H1,我在组件内</h1>",

styles:["h1{color:red}"]

})

class EzApp{}

varinjectables = [

bind(ShadowDomStrategy)

.toFactory((doc)=> new EmulatedScopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN])

];

bootstrap(EzApp,injectables);

</script>

</body>

</html>

属性声明 - 暴露成员变量

属性是组件暴露给外部世界的调用接口,调用者通过设置不同的属性值来定制组件的行为与外观:

Angular2中为组件增加属性接口非常简单,只需要在Component注解properties属性中声明组件的成员变量就可以了:

1. //EzCard

2. @Component({

3. properties:["name","country"]

4. })

上面的代码将组件的成员变量namecountry暴露为同名属性,这意味着在EzApp的模板中,可以直接使用中括号语法来设置EzCard对象的属性:

1. //EzApp

2. @View({

3. directives :[EzCard],

4. template:"<ez-card [name]="'雷锋'"[country]="'中国'"></ez-card>"

5. })

提醒:如果要在模板中使用自定义的指令(组件是一种指令),必须在View注解directives属性中提前声明!

修改示例代码中EzApp组件的模板,为EzCard调用添加namecountry属性!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>Property</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap} from "angular2/angular2";

//根组件 - EzApp

@Component({selector:"ez-app"})

@View({

directives:[EzCard],

template:`

<divclass="ez-app">

<h1>EzApp</h1>

<ez-card></ez-card>

</div>`

})

class EzApp{}

//具有属性接口的组件 - EzCard

@Component({

selector:"ez-card",

properties:["name","country"]

})

@View({

template : `<divclass='ez-card'>

My name is<b>{{name}}</b>,

I am from<b>{{country}}</b>.</div>`

})

class EzCard{

constructor(){

this.name ="Mike";

this.country ="Sweden";

}

}

//渲染组件

bootstrap(EzApp);

</script>

<style>

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;}

</style>

</body>

</html>

事件声明 - 暴露事件源

属性相反,事件从组件的内部流出,用来通知外部世界发生了一些事情:

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:"<ez-card (change)="onChange()"></ez-card>"
4.})

每次EzCard触发change事件时,EzApponChange()方法都将被调用。

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>hello,angular2</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap,EventEmitter} from"angular2/angular2";

//根组件 - EzApp

@Component({selector:"ez-app"})

@View({

directives:[EzCard],

template:`

<divclass="ez-app">

<h1>EzApp</h1>

<ez-card(change)="onChange($event)"></ez-card>

<pre>{{evtStr}}</pre>

</div>`

})

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 : `<divclass='ez-card'>

My name is<b>{{name}}</b>,

I am from <b>{{country}}</b>.</div>`

})

class EzCard{

constructor(){

this.name ="Mike";

this.country ="Sweden";

this.change = newEventEmitter();

//模拟触发事件

setTimeout(()=>this.change.next({

src:"EzCard",

desc:"模拟事件"

}),1000);

}

}

//渲染组件

bootstrap(EzApp);

</script>

<style>

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;}

</style>

</body>

</html>

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指令,并在反馈中显示所选择的搜索类别!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>NgForm</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap,NgIf}from "angular2/angular2";

//引入form指令集

import {formDirectives} from "angular2/forms";

//EzApp组件

@Component({selector:"ez-app"})

@View({

directives:[formDirectives,NgIf],

template:`

<form#f="form" (submit)="search(f.value)">

<select>

<option value="web">网页</option>

<option value="news">新闻</option>

<option value="image">图片</option>

</select>

<input type="text"ng-control="kw">

<buttontype="submit">搜索</button>

</form>

<!--给个简单的反馈-->

<h1*ng-if="kw!=''">正在搜索 {{kw}}...</h1>

`,

styles:[`form{background:#90a4ae;padding:5px;}`]

})

class EzApp{

constructor(){

this.kw = "";

}

search(val){

this.kw = val.kw;

//假装在搜索,2秒钟返回

setTimeout(()=>this.kw="",2000);

}

}

bootstrap(EzApp);

</script>

</body>

</html>

NgControlName - 命名控件指令

如前所述,NgControlName指令必须作为NgFormNgFormModel的后代使用,因为这个指令需要将创建的控件对象添加到祖先(NgFormNgFormModel)所创建控件组中。

NgControlName指令的选择符是[ng-control],这意味着你必须在一个HTML元素上定义ng-control属性,这个指令才会起作用。

属性:ngControl

NgControlName指令为宿主的DOM对象创建一个控件对象,并将这个对象以ngControl属性指定的名称绑定到DOM对象上:

1.<form#f="form">
2.<inputtype="text"ng-control="user">
3.<inputtype="password"ng-control="pass">
4.</form>

在上面的代码中,将创建两个Control对象,名称分别为userpass

属性/方法:ngModel

除了使用控件组获得输入值,NgControlName指令可以通过ngModel实现模型与表单的双向绑定:

1.<form>
2.<inputtype="text"ng-control="user"[(ng-model)]="data.user">
3.<inputtype="password"ng-control="pass"[(ng-model)]="data.pass">
4.</form>`

ngModel即是NgControlName指令的属性,也是它的事件,所以下面的两种写法是等价的:

1.<inputtype="text"ng-control="user"[(ng-model)]="data.user">
2.//等价于
3.<inputtype="text"ng-control="user"[ng-model]="data.user"(ng-model)="data.user">

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>NgControlName</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import{Component,View,bootstrap,NgIf} from "angular2/angular2";

import {formDirectives} from "angular2/forms";

@Component({selector:"ez-app"})

@View({

directives:[NgIf,formDirectives],

template:`

<form>

<ul>

<li>姓名:<inputtype="text" ng-control="name"[(ng-model)]="data.name"></li>

<li>姓别:

<select ng-control="gender"[(ng-model)]="data.gender">

<option value="Male">男</optoin>

<option value="Female">女</optoin>

</select>

</li>

<li>地址:<inputtype="text" ng-control="address"[(ng-model)]="data.address"></li>

<li>电话:<inputtype="text" ng-control="telephone"[(ng-model)]="data.telephone"></li>

<li>已婚:<inputtype="checkbox" ng-control="marriage" [(ng-model)]="data.marriage"></li>

</ul>

</form>

<pre>{{decode(data)}}</pre>

`,

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);

</script>

</body>

</html>

NgCongrolGroup - 命名控件组

NgControlGroup指令的选择符是[ng-control-group],如果模板中的某个元素具有这个属性, Angular2框架将自动创建一个控件组对象,并将这个对象以指定的名称DOM对象绑定。

控件组可以嵌套,方便我们在语义上区分不同性质的输入:

NgControlName指令一样,NgControlGroup指令也必须作为NgFormNgFormModel后代使用,因为这个指令需要将创建的控件组对象添加到祖先(NgFormNgFormModel)所创建控件组中。

在示例代码中再增加一个控件组,采集关于用户的兴趣爱好方面的信息!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>NgControlGroup</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript" src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap,NgIf}from "angular2/angular2";

import {formDirectives} from "angular2/forms";

@Component({selector:"ez-app"})

@View({

directives:[NgIf,formDirectives],

template:`

<form#f="form">

<div>基本信息</div>

<!--声明控件组-->

<ul ng-control-group="basic">

<li>姓名:<inputtype="text" ng-control="name"></li>

<li>地址:<inputtype="text" ng-control="address"></li>

<li>电话:<inputtype="text" ng-control="telephone"></li>

</ul>

<div>专业技能</div>

<!--声明控件组-->

<ulng-control-group="expertise">

<li>英语:<inputtype="checkbox" ng-control="english"></li>

<li>科技:<inputtype="checkbox" ng-control="tech"></li>

<li>运动:<inputtype="checkbox" ng-control="sport"></li>

</ul>

</form>

<!--调试:实时转储模型的值-->

<pre>{{decode(f.value)}}</pre>

`,

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);

</script>

</body>

</html>

NgFormControl- 绑定已有控件对象

NgControlName指令不同,NgFormControl将已有的控件/Control对象绑定到DOM元素上。当需要对输入的值进行初始化时,可以使用NgFormControl指令。

下面的代码中,使用NgFormControl指令将DOM元素绑定到组件EzComp的成员变量movie上,我们需要在构造函数中先创建这个Control对象:

1. @View({

2. //将输入元素绑定到已经创建的控件对象上

3. template:`<input type="text"[ng-form-control]="movie">`

4. })

5. classEzComp{

6. constructor(){

7. //创建控件对象

8. this.movie =newControl("Matrix II -Reload");

9. }

10. }

控件/ControlAngular2中对表单输入元素的抽象,我们使用其value属性,就可以获得对应的输入元素的值。

NgControlName指令的另一个区别是,NgFormControl不需要NgFormNgFormModel的祖先。

为示例代码增加采集用户工作单位的输入项,并在调试信息中显示!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>NgFor</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript" src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import {Component,View,bootstrap} from"angular2/angular2";

import {Control,formDirectives} from "angular2/forms";

@Component({selector:"ez-app"})

@View({

directives:[formDirectives],

template:`

<div>

<ul>

<!--将输入元素绑定到已经创建的控件对象-->

<li>姓名:<input type="text"[ng-form-control]="name"></li>

<li>地址:<inputtype="text" [ng-form-control]="address"></li>

<li>电话:<inputtype="text" [ng-form-control]="telephone"></li>

</ul>

</div>

<!--调试:转储模型信息-->

<pre>{{dump()}}</pre>

`,

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);

</script>

</body>

</html>

NgFormModel- 绑定已有控件组

NgFormModel指令类似于NgControlGroup指令,都是为控件提供容器。但区别在于,NgFormModel指令将已有的控件组绑定到DOM对象上:

1. @View({

2. template:`

3. <!--绑定控件组与控件对象-->

4. <div[ng-form-model]="controls">

5. <input type="text"ng-control="name">

6. <input type="text"ng-control="age">

7. </div>`

8. })

9. classEzComp{

10. constructor(){

11. //创建控件组及控件对象

12. this.controls =newControlGroup({

13. name :newControl("Jason"),

14. age :newControl("45")

15. });

16. }

17. }

NgFormModel指令可以包含NgControlGroup指令,以便将不同性质的输入分组。

将示例代码中的地址和电话输入元素,使用NgControlGroup指令放入单独的一个分组中。

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>NgFor</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import{Inject,Component,View,bootstrap} from "angular2/angular2";

import {Control,ControlGroup,formDirectives} from"angular2/forms";

@Component({selector:"ez-app"})

@View({

directives:[formDirectives],

template:`

<div[ng-form-model]="controls">

<ul>

<li>姓名:<inputtype="text" ng-control="name"></li>

<li>地址:<inputtype="text" ng-control="address"></li>

<li>电话:<inputtype="text" ng-control="telephone"></li>

</ul>

</div>

<pre>{{dump()}}</pre>

`,

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);

</script>

</body>

</html>

服务 - 封装可复用代码

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依赖于这个服务:

修改右边示例代码,实现一个减法计算器!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>Service</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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 : `

<form>

<input type="text" ng-control="a"[(ng-model)]="a">

+

<inputtype="text" ng-control="b" [(ng-model)]="b">

=

{{add()}}

</form>`

})

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);

</script>

<style>

*{font-size:30px;font-weight:bold;}

input{width:100px;}

</style>

</body>

</html>

注入 - appInjector

在前一节的示例代码中,组件EzAlgo直接在构造函数中实例化了一个EzAlog对象,这造成了EzAppEzAlgo的强耦合,我们可以使用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时注入,分别在什么情况下适用?

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>appInjector</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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 : `

<form>

<input type="text" ng-control="a"[(ng-model)]="a">

+

<inputtype="text" ng-control="b" [(ng-model)]="b">

=

{{add()}}

</form>`,

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);

</script>

</body>

</html>

注入一个复杂的服务

EzAlgo相当简单,使用new或者使用Injector来获得一个实例看起来差别不大。那如果我们EzApp组件要使用Http服务呢?

第一眼看上去,Http服务显然是一个真正有用的服务 - 因为看起来相当的复杂 -Http依赖于XHRBackendBaseRequestOptions,而XHRBackend又依赖于BrowserXHR

我们可以有两种方法获得一个Http的实例,以便通过它获得网络访问的功能:

1. 使用new进行实例化

如果我们使用传统的new方式创建Http实例,看起来应该是这样:

1.varxhrbe=newXHRBackend(BrowserXHR);
2.varoptions=newBaseRequestOptions();
3.varhttp=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()方法订阅以后,每当数据流中有新的数据,你都会得到通知。

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>appInjector</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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:`

<div *ng-for="#albumof band.albums"><img [src]="album.cover"></div>

`,

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);

</script>

<style>

body{background:black}

</style>

</body>

</html>

路由 - 初体验

一个Web应用通常需要切割为多个不同的组件进行实现,然后根据用户的交互行为(通常是点击),动态地载入不同的组件,根据用户行为选择组件的过程就是路由

由于Angular2是面向组件的,所以,Angular2的路由其实就是组件之间的路由

点击示例页面的videomusic,体会Angular2路由的作用!

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>Router</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<!--注册Angular2路由模块库-->

<script type="text/javascript"src="lib/router.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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 : `

<nav>

<!--声明路由入口-->

<brouter-link="video">video</b> |

<brouter-link="music">music</b>

</nav>

<main>

<!--声明路由出口-->

<router-outlet></router-outlet>

</main>

`

})

//路由配置

@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 : `

<h3>视频:轻松一刻</h3>

<embedallowscriptaccess="always" height="482"pluginspage="http://get.adobe.com/cn/flashplayer/"flashvars="list=http%3A%2F%2Fus.sinaimg.cn%2F002TFmqWjx06TNSxqGuz0504010000220k01.m3u8%3FKID%3Dunistore%2Cvideo%26Expires%3D1437020410%26ssig%3DNnoWLSjm2v&amp;fid=1034:882b42973162c7ae7acef23bfaccbe8c&amp;logo=2&amp;uid=1971237631"allowfullscreen="true" width="482" quality="high"src="http://js.t.sinajs.cn/t5/album/static/swf/video/player.swf?v1423545453000454353"type="application/x-shockwave-flash"wmode="transparent">

`

})

class EzVideo{}

//EzMusic组件

@Component({selector:"ez-music"})

@View({

template : `

<h3>音乐:平凡之路</h3>

<embedsrc="http://player.yinyuetai.com/video/player/2094298/a_0.swf"quality="high" width="480" height="334"align="middle" allowScriptAccess="sameDomain"allowfullscreen="true" type="application/x-shockwave-flash"></embed>                            `

})

class EzMusic{}

//注入路由功能依赖项

bootstrap(EzApp,[routerInjectables]);

</script>

<style>

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;}

</style>

</body>

</html>

路由 - 应用步骤

Angular2中使用路由的功能,有三个步骤:

1. 配置路由

为组件注入Router对象并通过config()方法配置路由:

1. router.config([

2. {path:"/video", component:EzVideo},

3. {path:"/music", component:EzMusic}])

上面的代码中,配置了两条路由:

  • 如果用户请求路径为/video,那么将在路由出口中激活组件EzVideo
  • 如果用户请求路径为/music,那么将在路由出口中激活组件EzMusic

2. 设置路由出口

路由出口是组件激活的地方,使用RouterOutlet指令在组件模板中声明出口:

1. @View({

2. directives:[RouterOutlet],

3. template:`<router-outlet></router-outlet>`

4. })

5. classEzApp{...}

3. 执行路由

使用Routernavigate()方法可以执行指定路径的路由,在示例中,当用户点击时我们调用这个方法进行路由:

1. @View({

2. template:`

3. <span(click)="router.navigate('/video')">video</span> |

4. <span(click)="router.navigate('/music')">music</span>

5. <router-outlet></router-outlet>`

6. })

我们向navigate()方法传入的路径,就是我们通过config()方法配置的路径。这样,Router就根据这个路径,找到匹配的组件,在RouterOutlet上进行激活。


在真正开始使用路由的功能之前,我们需要做一些准备工作:


1. 引用路由包

Angular2的路由模块单独打包在router.dev.js,因此首先应该引用这个包:

1. <scripttype="text/javascript"src="lib/router.dev.js"></script>

2. 引入路由相关的预定义类型

Angular2的路由模块名为angular2/router,我们从这里引入常用类型:

1. import{LocationStrategy,Router,RouterOutlet,routerInjectables}from"angular2/router";

3. 声明路由相关依赖类型

在启动组件时,我们需要声明路由相关的依赖类型(即变量:routerInjectables),以便根注入器可以解析对这些类型的请求:

1. bootstrap(EzApp,[routerInjectables]);

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>Router - steps</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<!--引入Angular2路由模块库-->

<script type="text/javascript"src="lib/router.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

import{Inject,Component,View,bootstrap} from "angular2/angular2";

import {LocationStrategy,RouterOutlet,Router,routerInjectables} from"angular2/router";

@Component({selector:"ez-app"})

@View({

directives:[RouterOutlet],

template : `

<nav>

<b (click)="go('/video')">video</b> |

<b (click)="go('/music')">music</b>

</nav>

<main>

<!--声明路由出口-->

<router-outlet></router-outlet>

</main>

`

})

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 : `

<h1>I LOVE THISVIDEO!</h1>

`

})

class EzVideo{}

@Component({selector:"ez-music"})

@View({

template : `

<h1>THAT'S FANTASTICMUSIC!</h1>

`

})

class EzMusic{}

bootstrap(EzApp,[routerInjectables]);

</script>

<style>

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;}

</style>

</body>

</html>

RouteConfig- 路由配置注解

除了使用Routerconfig()方法进行路由配置,Angular2还提供了路由注解,允许我们注解的方式为组件添加路由:

1. @RouteConfig([

2. {path:"/video", component:EzVideo},

3. {path:"/music", component:EzMusic}

4. ])

5. classEzApp{...}

RouteConfigAnnotation的构造函数参数与Routerconfig()方法参数一致,都是一个包含若干配置项的数组。

事实上,它的确只是一个语法糖 Angular2bootstrap一个组件时,会检查组件是否存在RouteConfig注解,如果存在,则利用这个信息调用Routerconfig()方法进行路由配置:

不过这样写起来,感觉会流畅一些,声明式的氛围更强烈一些。

修改示例代码:

1. 增加一个组件EzSport
2.
EzApp的路由配置注释中添加指向EzSport的路由,路径为"/sport"
3.
EzApp的模板中添加指向路径"/sport"的路由入口

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>RouteConfig</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<!--引入Angular2路由模块库-->

<script type="text/javascript"src="lib/router.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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: `

<!--声明路由入口-->

<nav>

<b (click)="go('/video')">video</b> |

<b (click)="go('/music')">music</b>

</nav>

<main>

<!--声明路由出口-->

<router-outlet></router-outlet>

</main>

`

})

//路由配置注解

@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 : `

<h1>I LOVE THISVIDEO!</h1>

`

})

class EzVideo{}

//EzMusic组件

@Component({selector:"ez-music"})

@View({

template : `

<h1>THAT'S FANTASTICMUSIC!</h1>

`

})

class EzMusic{}

bootstrap(EzApp,[routerInjectables]);

</script>

<style>

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;}

</style>

</body>

</html>

RouterLink- 路由入口指令

除了使用Routernavigate()方法切换路由,Angular2还提供了一个指令用来将一个DOM对象增强为路由入口

1. @View({

2. directives:[RouterOutlet,RouterLink]

3. template:`<nav>

4. <brouter-link="video">video</b> |

5. <brouter-link="music">music</b>

6. </nav>

7. <router-outlet></router-outlet>`

8. })

RouterLink指令为宿主DOM对象添加click事件监听,在触发时调用Routernavigate()方法进行路由。

路由项别名

需要指出的是,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组件

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>RouterLink</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<!--引入Angular2路由模块库-->

<script type="text/javascript"src="lib/router.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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 : `

<!--声明路由入口-->

<nav>

<b router-link="video">video</b> |

<b router-link="music">music</b>

</nav>

<main>

<!--声明路由出口-->

<router-outlet></router-outlet>

</main>

`

})

@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 : `

<h1>I LOVE THISVIDEO!</h1>

`

})

class EzVideo{}

//EzMusic组件

@Component({selector:"ez-music"})

@View({

template : `

<h1>THAT'S FANTASTICMUSIC!</h1>

`

})

class EzMusic{}

bootstrap(EzApp,[routerInjectables]);

</script>

<style>

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;}

</style>

</body>

</html>

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()方法

<!doctype html>

<html>

<head>

<metacharset="utf-8">

<title>RouteRegistry</title>

<script type="text/javascript"src="lib/system@0.16.11.js"></script>

<script type="text/javascript"src="lib/angular2.dev.js"></script>

<!--引入Angular2路由模块库-->

<script type="text/javascript"src="lib/router.dev.js"></script>

<script type="text/javascript"src="lib/system.config.js"></script>

</head>

<body>

<ez-app></ez-app>

<script type="module">

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 : `

<nav>

<!--声明路由入口-->

<b router-link="video">video</b> |

<b router-link="music">music</b>

</nav>

<main>

<!--声明路由出口-->

<router-outlet></router-outlet>

</main>

<footer>

<h3>matchers</h3>

<pre *ng-for="#matcher of dump('matchers')">{{matcher}}</pre>

<h3>redirects</h3>

<pre *ng-for="#redirect ofdump('redirects')">{{redirect}}</pre>

<h3>names</h3>

<pre *ng-for="#name ofdump('names')">{{name}}</pre>

</footer>

`

})

@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: `<h1>VIDEO</h1>`})

class EzVideo{}

//EzMusic组件

@Component({selector:"ez-music"})

@View({template : `<h1>MUSIC</h1>`})

class EzMusic{}

bootstrap(EzApp,[routerInjectables]);

</script>

<style>

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}

</style>

</body>

</html>

angularjs2学习教程相关推荐

  1. Vyond制作2D动画学习教程

    Vyond为2D动画提供了极其简单的分解视频创建过程. 你会学到什么 课程获取:Vyond制作2D动画学习教程-云桥网 您将学习如何为2d动画制作画外音 您将学习如何使用Vyond轻松创建精彩的动画视 ...

  2. MAYA 2022基础入门学习教程

    流派:电子学习| MP4 |视频:h264,1280×720 |音频:AAC,48.0 KHz 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:3.41 GB |时长:4.5小时 包含 ...

  3. 3dmax Vray建筑可视化入门学习教程

    面向初学者的3Ds Max Vray最佳Archviz可视化课程 从安装到最终图像的一切都将从头开始教授,不需要任何经验 大小解压后:3.25G 时长4h 6m 1280X720 MP4 语言:英语+ ...

  4. Unity 创建2D平台游戏开发学习教程

    了解如何使用C#在Unity中创建您的第一款2D平台游戏 你会学到什么 使用Unity创建2D奥运会 使用可脚本化的对象和单一模式 使用良好的编程实践 创造武器和射弹 使用可脚本化的对象和委托模式创建 ...

  5. Blender 3.0基础入门学习教程 Introduction to Blender 3.0

    成为Blender通才,通过这个基于项目的循序渐进课程学习所有主题的基础知识. 你会学到什么 教程获取:Blender 3.0基础入门学习教程 Introduction to Blender 3.0- ...

  6. UE5废墟破坏游戏场景创建学习教程

    为游戏创建毁坏的资产–深入教程课程 了解一个专业的环境艺术家在为游戏创建毁坏的资产时是如何工作的.您将学习正确的资产规划.创建模块化资产.创建损坏的混凝土和柱子.创建损坏的木材/地板.创建碎石堆.模拟 ...

  7. Maya游戏角色绑定入门学习教程 Game Character Rigging for Beginners in Maya

    准备好开始为游戏制作自己的角色动画了吗? 你会学到什么 了解Maya的界面 优化并准备好你的模型,为游戏做准备 了解关节以及如何使用它们来构建健壮的角色骨骼,以便在任何游戏引擎中制作动画 了解IK和F ...

  8. UE4材质着色器全面学习教程

    你会学到什么 通过所有着色器类型和设计的实际演示,学习创建材质 要求 对虚幻的基本理解会有所帮助 了解纹理的一般知识(不仅限于UE4)也很有用 描述 在这个系列中,我将带你设置大量不同的材料,教你如何 ...

  9. 虚幻引擎的数学知识学习教程 Math for Unreal Engine (Early Preview)

    通过做真实世界的 Unreal Engine项目来学习数学 你会学到什么 理解游戏开发对数学的基本需求 将数学直接应用到用例中,而不是钻研理论(用我们的示例项目进行实践) 正确编辑短视频,节省您的时间 ...

最新文章

  1. CSS3 Box-shadow 阴影效果用法
  2. flutter 泛型_Flutter/Dart - 泛型
  3. nagios 监控NFS
  4. hdu 2047(递推)
  5. RF无线电射频接口静电保护方案图
  6. ITK:Levenberg-Marquardt优化
  7. MySQL常见函数介绍
  8. filezilla 设置服务器_树莓派 LAMP服务器搭建
  9. 【编程大系】Java资源汇总
  10. nssl1187-排列【dp,随机卡常,树状数组】
  11. linux 系统盘无法ls,linux – ls:阅读目录.:输入/输出错误
  12. 使用双向链表构建二叉树_python:26.二叉搜索树与双向链表
  13. HDFS某个节点的磁盘满了
  14. mysql数据库 怎么替换_mysql数据库替换
  15. 更新计算机策略命令,组策略更新命令
  16. icem合并面网格_ICEM CFD中合并多个网格
  17. html自动生成拼音五笔,如何根据单元格汉字自动生成拼音码和五笔码
  18. 初中数学知识点总结_初中数学知识点
  19. 如何从iTunes Store赠送音乐,电影等
  20. PCB治具设计、制造和管理

热门文章

  1. MATLAB特殊矩阵的构造
  2. 硬盘IDE、SATA、AHCI模式的区别
  3. 解决4K屏下VMware虚拟机中界面太小问题
  4. 【算法】【链表模块】删除链表的中间节点或a/b节点
  5. 微信点击链接跳转到微信公众号关注页、微信关注链接
  6. 大雁牌计算机在线使用,用大雁牌计算器解决有关计算问题
  7. Python数据分析到底可以用来做什么?
  8. 铁大部分风景图片及文字描述。。。。
  9. ipad2019编写html,2019 iPad性能排行榜,选对型号很重要
  10. 计算机考研是属于工学呢还是理学呀,计算机考研是属于工学呢还是理学