一、 入門
1、初識Angular2
硬知識:Angular2與Angular的區別
(1)依賴加載:Angular1是依賴前置,angular2是按需加載
(2)數據綁定:
Angular1 在啓動時會給所有的異步交互點打補丁:
超時、
Ajax 請求、
瀏覽器事件、
Websockets,等等
在那些交互點,Angular 會對 scope 對象進行變動檢查,如果發現有變動就激發相應的監視器
重新運行變動檢查,檢查是否有更多的變化發生,重新運行監視器Angular 2 使用 zone.js 機制使摘要循環不再被需要。簡單的非 Angular 指定代碼可以透明地激發一個Angular 2 摘要。
zone.js的設計靈感來源於Dart語言,它描述JavaScript執行過程的上下文,可以在異步任務之間進行持久性傳遞,它類似於Java中的TLS(線程本地存儲)技術,zone.js則是將TLS引入到JavaScript語言中的實現框架。
寫一個Angular2的Hello World應用相當簡單,分三步走:
- 引入Angular2預定義類型
import {Component,View,bootstrap} from "angular2/angular2";
import是ES6的關鍵字,用來從模塊中引入類型定義。在這裏,我們從angular2模塊庫中引入了三個類型: Component類、View類和bootstrap函數。
- 實現一個Angular2組件
實現一個Angular2組件也很簡單,定義一個類,然後給這個類添加註解:
@Component({selector:"ez-app"})
@View({template:"<h1>Hello,Angular2</h1>"})
class EzApp{}
class也是ES6的關鍵字,用來定義一個類。@Component和@View都是給類EzApp附加的元信息, 被稱爲註解/Annotation。
@Component最重要的作用是通過selector屬性(值爲CSS選擇符),指定這個組件渲染到哪個DOM對象上。 @View最重要的作用是通過template屬性,指定渲染的模板。
- 渲染組件到DOM
將組件渲染到DOM上,需要使用自舉/bootstrap函數:
bootstrap(EzApp);
這個函數的作用就是通知Angular2框架將EzApp組件渲染到DOM樹上。
2、註解/Annotation
ES6規範裏沒有裝飾器。這其實利用了traceur的一個實驗特性:註解。給一個類 加註解,等同於設置這個類的annotations屬性:
//註解寫法
@Component({selector:"ez-app"})
class EzApp{...}
等同於:
class EzApp{...}
EzApp.annotations = [new Component({selector:"ez-app"})];
很顯然,註解可以看做編譯器(traceur)層面的語法糖,但和python的裝飾器不同, 註解在編譯時僅僅被放在annotation裏,編譯器並不進行解釋展開 - 這個解釋的工作是 Angular2完成的
二、組件開發–模板語法
1、最簡單的模板
有兩種方法爲組件指定渲染模板:
- 內聯模板
可以使用組件的View註解中的template屬性直接指定內聯模板:
@View({
template : `<h1>hello</h1>
<div>...</div>`
})
在ES6中,使用一對`符號就可以定義多行字符串,這使得編寫內聯的模板輕鬆多了。
- 外部模板
也可以將模板寫入一個單獨的文件:
<!--ezcomp-tpl.html-->
<h1>hello</h1>
<div>...</div>
然後在定義組件時,使用templateUrl引用外部模板:
@View({
templateUrl : "ezcomp-tpl.html"
})
2、directives - 使用組件
在Angular2中,一個組件的模板內除了可以使用標準的HTML元素,也可以使用自定義的組件!
這是相當重要的特性,意味着Angular2將無偏差地對待標準的HTML元素和你自己定義的組件。這樣, 你可以建立自己的領域建模語言了,這使得渲染模板和視圖模型的對齊更加容易,也使得模板的語義性 更強:
@Component({selector:"ez-app"})
@View({
directives:[EzCard],
template:`
<div class="ez-app">
<h1>EzApp</h1>
<ez-card></ez-card>
</div>`
})
class EzApp{}
@Component({selector : "ez-card"})
@View({
directives:[EzLogo],
template : `
<div class="ez-card">
<h1>EzCard</h1>
<ez-logo></ez-logo>
</div>`
})
class EzCard{}
@Component({selector : "ez-logo"})
@View({
template : `
<div class="ez-logo">
<h1>EzLogo</h1>
</div>`
})
class EzLogo{}
bootstrap(EzApp);
聲明要在模板中使用的組件
不過,在使用自定義組件之前,必需在組件的ViewAnnotation中通過directives屬性聲明這個組件:
@View({
directives : [EzComp],
template : "<ez-comp></ez-comp>"
})
你應該注意到了,directives屬性的值是一個數組,這意味着,你需要在這裏聲明所有你需要在模板 中使用的自定義組件。
3、{{model}} - 文本插值
在模板中使用可以{{表達式}}的方式綁定組件模型中的表達式,當表達式變化時, Angular2將自動更新對應的DOM對象:
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>
</div>
`
})
class EzApp{
constructor(){
this.title = "證監會:對惡意做空是有監測的";
this.date = "2015年07月11日 15:32:35";
this.source = "北京晚報";
}
}
bootstrap(EzApp);
4、[property] - 綁定屬性
在模板中,也可以使用一對中括號將HTML元素或組件的屬性綁定到組件模型的某個表達式, 當表達式的值變化時,對應的DOM對象將自動得到更新:
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';
this.d = ["red", "green", "blue", "yellow", "black", "grey"];
var self = this;
var num = 0;
setInterval(function () {
num++;
if (num + 1 == self.d.length) {
num = 0
}
self.color = self.d[num];
}, 500);
}
}
bootstrap(EzApp);
以上的代碼,h1標籤會每秒自動變顏色。
5、(event) - 監聽事件
在模板中爲元素添加事件監聽很簡單,使用一對小括號包裹事件名稱,並綁定 到表達式即可:
import {Component,View,bootstrap} from "angular2/angular2";
@Component({selector:"ez-app"})
@View({
template:`
<h1>Your turn! <b>{{sb}}</b></h1>
<button (click)="roulette()">ROULETTE</button>
`
})
class EzApp{
constructor(){
this.names = ["Jason","Mary","Linda","Lincoln","Albert","Jimmy"];
this.roulette();
}
//輪盤賭
roulette(){
var idx = parseInt(Math.random()*this.names.length);
this.sb = this.names[idx];
}
}
bootstrap(EzApp);
上面的代碼實例爲DOM對象h1的click事件添加監聽函數onClick()。
另一種等效的書寫方法是在事件名稱前加on-前綴:
@View({template : `<h1 on-click="onClick()">HELLO</h1>`})
6、#var - 局部變量
有時模板中的不同元素間可能需要互相調用,Angular2提供一種簡單的語法將元素 映射爲局部變量:添加一個以#或var-開始的屬性,後續的部分表示變量名, 這個變量對應元素的實例。
在下面的代碼示例中,我們爲元素h1定義了一個局部變量v_h1,這個變量指向 該元素對應的DOM對象,你可以在模板中的其他地方調用其方法和屬性:
@View({
template : `
<h1 #v_h1>hello</h1>
<button (click) = "#v_h1.textContent = 'HELLO'">test</button>
`
})
如果在一個組件元素上定義局部變量,那麼其對應的對象爲組件的實例:
@View({
directives:[EzCalc],
template : "<ez-calc #c></ez-calc>"
})
在上面的示例中,模板內的局部變量c指向EzCalc的實例。
三、條件邏輯
1、NgIf
有時我們需要模板的一部分內容在滿足一定條件時才顯示, NgIf發揮作用的場景,它評估屬性ngIf的值是否爲真,來決定是否渲染 template元素的內容:
@View({
template : `<!--根據變量trial的值決定是否顯示廣告圖片-->
<template *ngIf="trial==true">
<img src="ad.jpg">
</template>
<!--以下是正文-->
<pre>...
` })
2、ngSwitch
<container-element [ngSwitch]="switch_expression">
<some-element *ngSwitchCase="match_expression_1">...</some-element>
<some-element *ngSwitchCase="match_expression_2">...</some-element>
<some-other-element *ngSwitchCase="match_expression_3">...</some-other-element>
<ng-container *ngSwitchCase="match_expression_3">
<!-- use a ng-container to group multiple root nodes -->
<inner-element></inner-element>
<inner-other-element></inner-other-element>
</ng-container>
<some-element *ngSwitchDefault>...</some-element>
</container-element>
3、ngFor
<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>
<li template="ngFor let item of items; let i = index; trackBy: trackByFn">...</li>
NgFor provides several exported values that can be aliased to local variables:
- index will be set to the current loop iteration for each template context.
- first will be set to a boolean value indicating whether the item is the first one in the iteration.
- last will be set to a boolean value indicating whether the item is the last one in the iteration.
- even will be set to a boolean value indicating whether this item has an even index.
- odd will be set to a boolean value indicating whether this item has an odd index.
四、屬性與事件聲明
1、屬性聲明–暴露成員變量
屬性是組件暴露給外部世界的調用接口,調用者通過設置不同的屬性值來定製 組件的行爲與外觀:
在Angular2中爲組件增加屬性接口非常簡單,只需要在Component註解的 properties屬性中聲明組件的成員變量就可以了:
//EzCard
@Component({
properties:["name","country"]
})
上面的代碼將組件的成員變量name和country暴露爲同名屬性,這意味着在EzApp 的模板中,可以直接使用中括號語法來設置EzCard對象的屬性:
//EzApp
@View({
directives : [EzCard],
template : "<ez-card [name]="'雷鋒'" [country]="'中國'"></ez-card>"
})
提醒:如果要在模板中使用自定義的指令(組件是一種指令),必須在View註解的directives 屬性中提前聲明!
示例代碼:爲EzCard調用添加name和country屬性!
import {Component,View,bootstrap} from "angular2/angular2";
//根組件 - EzApp
@Component({selector:"ez-app"})
@View({
directives:[EzCard],
template:`
<div class="ez-app">
<h1>EzApp</h1>
<ez-card [name]="'frank'" [country]="'China'"></ez-card>
</div>`
})
class EzApp{}
//具有屬性接口的組件 - EzCard
@Component({
selector:"ez-card",
properties:["name","country"]
})
@View({
template : `<div class='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);
2、事件聲明 - 暴露事件源
與屬性相反,事件從組件的內部流出,用來通知外部世界發生了一些事情:
在Angular2中爲組件增加事件接口也非常簡單:定義一個事件源/EventEmitter, 然後通過Component註解的events接口包括出來:
//EzCard
@Component({
events:["change"]
})
class EzCard{
constructor(){
this.change = new EventEmitter();
}
}
上面的代碼將組件EzCard的事件源change暴露爲同名事件,這意味着在調用者 EzApp組件的模板中,可以直接使用小括號語法掛接事件監聽函數:
//EzApp
@View({
template : "<ez-card (change)="onChange()"></ez-card>"
})
每次EzCard觸發change事件時,EzApp的onChange()方法都將被調用。
import {Component,View,bootstrap,EventEmitter} from "angular2/angular2";
//根組件 - EzApp
@Component({selector:"ez-app"})
@View({
directives:[EzCard],
template:`
<div class="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 : `<div class='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 = new EventEmitter();
//模擬觸發事件
setTimeout(()=>this.change.next({
src:"EzCard",
desc:"模擬事件"
}),1000);
}
}
//渲染組件
bootstrap(EzApp);
五、form
1、NgForm - 表單指令
NgForm指令爲表單元素/form建立一個控件組對象,作爲控件的容器; 而NgControlName指令爲則爲宿主input元素建立一個控件對象,並將該控件加入到NgForm 指令建立的控件組中:
局部變量
通過使用#符號,我們創建了一個引用控件組對象(注意,不是form元素!)的局部變量f。 這個變量最大的作用是:它的value屬性是一個簡單的JSON對象,鍵對應於input元素的 ng-control屬性,值對應於input元素的值:
聲明指令依賴
NgForm指令和NgControlName指令都包含在預定義的數組變量formDirectives中,所以我們在 組件註解的directives屬性中直接聲明formDirectives就可以在模板中直接使用這些指令了:
//angular2/ts/src/forms/directives.ts
export const formDirectives = CONST_EXPR([
NgControlName,
NgControlGroup,
NgFormControl,
NgModel,
NgFormModel,
NgForm,
NgSelectOption,
DefaultValueAccessor,
CheckboxControlValueAccessor,
SelectControlValueAccessor,
NgRequiredValidator
]);
爲示例代碼中的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:`
<form #f="form" (submit)="search(f.value)">
<select ng-control="kw2">
<option selected value="web">網頁</option>
<option value="news">新聞</option>
<option value="image">圖片</option>
</select>
<input type="text" ng-control="kw">
<button type="submit">搜索</button>
</form>
<!--給個簡單的反饋-->
<h1 *ng-if="kw!=''">正在搜索 {{kw}} {{kw2}}</h1>
`,
styles:[`form{background:#90a4ae;padding:5px;}`]
})
class EzApp{
constructor(){
this.kw = "";
this.kw2="";
}
search(val){
this.kw = val.kw;
this.kw2 = val.kw2;
//假裝在搜索,2秒鐘返回
setTimeout(()=>this.kw="",2000);
}
}
bootstrap(EzApp);
2、NgControlName - 命名控件指令
如前所述,NgControlName指令必須作爲NgForm或NgFormModel的後代使用, 因爲這個指令需要將創建的控件對象添加到祖先(NgForm或NgFormModel)所創建 的控件組中。
NgControlName指令的選擇符是[ng-control],這意味着你必須在一個HTML元素上 定義ng-control屬性,這個指令纔會起作用。
屬性:ngControl
NgControlName指令爲宿主的DOM對象創建一個控件對象,並將這個對象以ngControl屬性 指定的名稱綁定到DOM對象上:
<form #f="form">
<input type="text" ng-control="user">
<input type="password" ng-control="pass">
</form>
在上面的代碼中,將創建兩個Control對象,名稱分別爲user和pass。
屬性/方法:ngModel
除了使用控件組獲得輸入值,NgControlName指令可以通過ngModel實現模型 與表單的雙向綁定:
<form>
<input type="text" ng-control="user" [(ng-model)]="data.user">
<input type="password" ng-control="pass" [(ng-model)]="data.pass">
</form>`
ngModel即是NgControlName指令的屬性,也是它的事件,所以下面 的兩種寫法是等價的:
<input type="text" ng-control="user" [(ng-model)]="data.user">
//等價於
<input type="text" ng-control="user" [ng-model]="data.user" (ng-model)="data.user">
3、NgCongrolGroup - 命名控件組
NgControlGroup指令的選擇符是[ng-control-group],如果模板中的某個元素具有這個屬性, Angular2框架將自動創建一個控件組對象,並將這個對象以指定的名稱與DOM對象綁定。
控件組可以嵌套,方便我們在語義上區分不同性質的輸入:
和NgControlName指令一樣,NgControlGroup指令也必須作爲NgForm或NgFormModel的 後代使用,因爲這個指令需要將創建的控件組對象添加到祖先(NgForm或NgFormModel)所創建 的控件組中。
<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>姓名:<input type="text" ng-control="name"></li>
<li>地址:<input type="text" ng-control="address"></li>
<li>電話:<input type="text" ng-control="telephone"></li>
</ul>
<div>專業技能</div>
<!--聲明控件組-->
<ul ng-control-group="expertise">
<li>英語:<input type="checkbox" ng-control="english"></li>
<li>科技:<input type="checkbox" ng-control="tech"></li>
<li>運動:<input type="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){
return JSON.stringify(val,null,"\t");
}
}
bootstrap(EzApp);
</script>
輸出結果:
{
"basic": {
"name": "123",
"address": "123",
"telephone": "123"
},
"expertise": {
"english": true,
"tech": true,
"sport": true
}
}
4、NgFormControl - 綁定已有控件對象
與NgControlName指令不同,NgFormControl將已有的控件/Control對象綁定到DOM元素 上。當需要對輸入的值進行==初始化==時,可以使用NgFormControl指令。
下面的代碼中,使用NgFormControl指令將DOM元素綁定到組件EzComp的成員 變量movie上,我們需要在構造函數中先創建這個Control對象:
@View({
//將輸入元素綁定到已經創建的控件對象上
template : `<input type="text" [ng-form-control]="movie">`
})
class EzComp{
constructor(){
//創建控件對象
this.movie = new Control("Matrix II - Reload");
}
}
控件/Control是Angular2中對錶單輸入元素的抽象,我們使用其value屬性,就可以獲得對應的 輸入元素的值。
與NgControlName指令的另一個區別是,NgFormControl不需要NgForm或NgFormModel的祖先。
<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>地址:<input type="text" [ng-form-control]="address"></li>
<li>電話:<input type="text" [ng-form-control]="telephone"></li>
<li>工作:<input type="text" [ng-form-control]="company"></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 = new Control("Jason");
this.address = new Control("London U.K.");
this.telephone = new Control("114");
this.company = new Control("114");
}
dump(){
//讀取控件對象的值
var val = {
name : this.name.value,
address : this.address.value,
telephone : this.telephone.value,
company : this.company.value
}
return JSON.stringify(val,null,"\t");
}
}
bootstrap(EzApp);
</script>
5、NgFormModel - 綁定已有控件組
NgFormModel指令類似於NgControlGroup指令,都是爲控件提供容器。但區別在於, NgFormModel指令將已有的控件組綁定到DOM對象上:
@View({
template : `
<!--綁定控件組與控件對象-->
<div [ng-form-model]="controls">
<input type="text" ng-control="name">
<input type="text" ng-control="age">
</div>`
})
class EzComp{
constructor(){
//創建控件組及控件對象
this.controls = new ControlGroup({
name :new Control("Jason"),
age : new Control("45")
});
}
}
NgFormModel指令可以包含NgControlGroup指令,以便將不同性質的輸入分組。
六、 @input和@output
先做個比方,然後奉上代碼比如:
<talk-cmp [talk]="someExp" (rate)="eventHandler($event.rating)">
input:
[talk]=”someExp” 這個標籤可以理解爲一個專門的監聽器,監聽父組件傳遞過來的someExp參數,並存入自身組件的talk變;好像是開了個後門,允許且只允許父組件的someExp進入,一旦進入立刻抓進一個叫talk的牢房,然後==子組件==中就可以通過@Input來定義這個變量talk然後使用它。
output:
(rate)=”eventHandler(event.rating) 這個意思是, 當子組件的click事件被觸發,就執行父組件的eventHandler函數,並把子組件的參數 event.rating傳遞給父組件的eventHandler函數;就好像,當小孩子一哭(執行click事件),他的母親立刻把他抱在懷裏(執行母親的eventHandler),同時母親獲得了小孩子的一些參數(event.rating)
1、@input()
父組件 father.component.ts 提供數據
import {Component} from "@angular/core";
@Component({
selector: "my-father",
templateUrl: "father.html"
})
export class FatherComponent {
data: Array<Object>;
constructor() {
this.data = [
{
"id": 1,
"name": "html"
},
{
"id": 2,
"name": "css"
},
{
"id": 3,
"name": "angular"
},
{
"id": 4,
"name": "ionic"
},
{
"id": 5,
"name": "node"
}
]
}
}
模板文件 father.html
<h1>父組件</h1>
// 包含子組件, 並使用屬性傳遞數據過去
<my-child [info]="data"></my-child>
子組件 child.component.ts 獲取數據
import {Component, Input} from "@angular/core";
@Component({
selector: "my-child",
templateUrl: "child.html"
})
export class ChildComponent {
// 使用@Input獲取傳遞過來的數據
@Input()
info: Array<Object>;
constructor() {
}
}
子組件 child.html模板文件
<ul>
<li *ngFor="let item of info">
{{item.name}}
</li>
</ul>
2、@Output()
子組件three-link.component.ts
- 引入
import {Component, OnInit, Output, EventEmitter} from "@angular/core";
- 定義輸出變量
export class ThreeLinkComponent {
province: string;
// 輸出一下參數
@Output() provinceOut = new EventEmitter();
constructor() {
this.province = "陝西";
}
}
- 事件出發,發射變量給父組件
provinceChange() {
// 選擇省份的時候發射省份給父組件
this.provinceOut.emit(this.province);
}
父組件模板
<!--三級聯動組件-->
<three-link (provinceOut)="recPro($event)"></three-link>
父組件
// 函數接受子函數傳遞過來的變量, 子函數中emit的時候觸發這個函數。
recPro(event) {
this.province = event;
}