Angular學習筆記(2)——TODO小應用
1. 寫在前面
之前我們跑了Angular的Hello World,你是不是對它有點感覺了呢?這一篇將結合一個TODO程序來繼續學習Angular的用法。
梳理一下之前的Hello World程序。我們寫了一個main.ts
來引導模塊AppModule
,而該模塊又包含組件AppComponent
,這是一個Angular應用最基本的結構。下面再來簡單地看看Angular各部件的含義。
先看Module(模塊)。Angular應用是模塊化的,每一個Angular應用至少有一個模塊,被稱爲根模塊,通常我們會以AppModule
來命名。要想定義一個模塊類,只需要在類上加上@NgModule
裝飾器。@NgModule
還接收一個元數據對象,該對象有如下幾個重要的屬性,其中前3個在Hello World中已經見到過:
declarations
: 聲明本模塊中擁有的視圖類。 Angular 有三種視圖類:組件、指令和管道。
bootstrap
: 指定應用的主視圖(稱爲根組件),它是所有其它視圖的宿主。只有根模塊才能設置bootstrap屬性。imports
: 導入其他模板類。exports
: 導出本模塊的視圖類。providers
: 服務的創建者,並加入到全局服務列表中,可用於應用任何部分。
接下來是Component(組件)。組件是負責控制UI上一小塊區域的部件,比如一個用戶列表,一個標題欄等,我們把這一小塊區域稱之爲視圖。要想定義一個組件,我們只需要在組件類的上面添加@Component
裝飾器,裝飾器接受的元數據包含視圖的名字(selector
),視圖的模板(template
或templateUrl
),視圖的樣式(styleUrls
)等屬性。而在組件類裏面,我們會定義一些與視圖相關的屬性和視圖的邏輯操作。
以上的兩個概念Module和Component我們在Hello World中都有些體會,在接下來的TODO例子中會介紹模板語法,主要介紹數據綁定,它的意思就是將變量視圖綁定起來,這麼做的好處就是一旦變量發生變化或者視圖發生變化,它們都會“同步更新”對方(雙向數據綁定)。
2. TODO應用
好了,不碼概念了,概念聽得越多越糊塗。還是來看實際的例子比較靠譜。點擊這裏查看演示效果。
這次我們不自己手動建工程了,採用另一種方式:使用git
克隆Angular的quickstart項目,在此之上進行開發。
使用如下命令進行克隆:
git clone https://github.com/angular/quickstart angular-todo
用idea或其他IDE打開angular-todo
工程,在從命令行輸入npm install
安裝依賴庫。接着運行npm start
,官方的Hello World就跑起來了,我們幾乎不用修改它寫好的代碼(app.module.ts
除外),只需在上面添加代碼即可。
採用Angular模塊化的思維去分析TODO應用,由於功能很簡單,我們沒必要建立新的模塊,只需要建立一個TODO組件即可,頁面分爲如下形式:
下面來實現TodoComponent
。首先是新建todo.component.ts
、todo.component.html
、todo.component.css
三個文件。然後在todo.component.ts
定義出TodoComponent
,並指定模板(通過templateUrl
屬性)和樣式(通過styleUrls
屬性):
import {Component} from '@angular/core';
@Component({
moduleId: module.id,
selector: 'todo',
templateUrl: 'todo.component.html',
styleUrls: ['todo.component.css']
})
export class TodoComponent {
}
以上和Hello World中的代碼沒有多少區別,相信很容易看懂。Todo組件先寫到這,我們現在將它聲明到AppModule
中:
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import {AppComponent} from './app.component';
import {TodoComponent} from './todo.component';
@NgModule({
imports: [BrowserModule, FormsModule],
declarations: [AppComponent, TodoComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
注意,以上還引入了官方的FormsModule
模塊,它用於form元素的相關操作,我們待會會用到,這裏提前引入。現在,我們就可以在AppComponent
中使用自定義的todo組件了。和之前分析的結構一樣,模板中先是一個標題,再是todo組件,然後添加點樣式:
import {Component} from '@angular/core';
@Component({
selector: 'my-app',
template: `
<div class="main-container">
<h1>TODO</h1>
<todo></todo>
</div>
`,
styles:[`.main-container{width: 960px; margin: 0 auto;}`]
})
export class AppComponent {
}
現在基本的骨架就OK了,接下來的重點就是todo組件的具體編寫了。之前有提過,使用Angular等數據驅動的框架和寫jQuery等庫最大的不同在於Angular是以數據爲中心,任何變化的視圖(或者不變的視圖,只要你願意)背後必然對應着相應的數據結構。拿我們的todo應用來說,視圖中的todo列表的背後就對應着一個數組,todo列表的每一項就對應數組中的某一個位置上的元素。如果我們想添加或刪除todo列表項,那麼只需要對對應的數組進行添加或刪除操作;如果我們想修改todo列表的某一項的信息,我們只需要修改對應的數組的對應位置上的todo對象即可。要知道,我們用JS修改一個變量,要比直接修改DOM輕鬆的多。如果你是第一次使用Angular這類採用數據驅動的框架,你一定會覺得It's amazing,媽媽再也不用擔心我拼接html字符串了。
有了上面的分析,我們先要將一個todo項的數據結構定義出來,因此在todo.component.ts
中,添加如下代碼:
class Item {
content: string; // todo內容
date: Date; // todo創建日期
done: boolean; // 是否完成
constructor(content: string) {
this.content = content;
this.date = new Date();
}
}
然後在TodoComponent
類中,我們需要定義一個Item
數組,表示我們的todo列表,並且我們還要需要一個添加todo項的方法:
export class TodoComponent {
todoList: Item[] = [];
addTodo(value: any): void {
this.todoList.push(new Item(value));
}
}
OK,TodoComponent類暫時就完成了,代碼很簡單,就不做說明了。接下來就是todo組件模板和樣式的編寫。模板代碼其實和我們平常寫的html代碼差不多,不同之處在於加了一些Angular自定義的語法,用於控制頁面的渲染,模板代碼如下:
<p>
<label for="todo-input">Todo: </label>
<input id="todo-input" type="text" #input (keyup.enter)="addTodo(#input.value)"/>
</p>
<label>List: </label>
<ol>
<p *ngIf="todoList.length == 0">Empty...</p>
<li *ngFor="let item of todoList" [class.done]="item.done">
<label>
<input type="checkbox" [(ngModel)]="item.done">
<span [style.color]="item.done ? 'gray' : 'green'">{{item.content}}</span>
-
<span class="hint">{{item.date | date:"yyyy-MM-dd HH:mm:ss"}}</span>
</label>
</li>
</ol>
要重點說說上面的模板語法。首先最先讓你困惑的是這句,
<input id="todo-input" type="text" #input (keyup.enter)="addTodo(input.value)"/>
#input
是什麼鬼?其實它的官方叫法是template reference variable(模板引用變量),它用於在模板中對DOM元素或指令的引用。這裏#input
就是用input
變量來引用它所在的input元素(即<input .../>
),因此在後面(keyup.enter)="addTodo(input.value)"
中,我們可以用input.value
拿到輸入框輸入的值。那麼keyup.enter
又是什麼鬼呢?它其實被稱爲Event binding(事件綁定)。爲了說清楚事件綁定的概念,我們把它拆開來看,一個是事件,一個是綁定。
先說事件。這和原生js一樣,比如鼠標點擊(click),上面的按鍵後鬆開(keyup)等,都是事件。並且Angular還能自定義事件,這個先不做說明。上面的keyup.enter
事件會在鍵盤enter
鍵擡起時觸發,觸發後會調用我們在TodoComponent
中寫好的addTodo方法,並把input.value
作爲參數傳遞進去。
再說綁定。之前已經多次提到過,綁定實際上就是把數據與視圖,事件與方法等關聯起來,一方發生變化或被觸發,另一方自動地隨之做出變化或被觸發。綁定是有方向的,這個方向指的是數據流動的方向,因此可分爲如下三類:
這三類綁定方式的語法分別是[]
,()
,[()]
:
// 第一種,數據源 -> 視圖
<img [src] = "user.avatar">
// 第二種,視圖 -> 數據源
<button (click) = "delete()">刪除</button>
// 第三種,視圖 <-> 數據源
<input [(ngModel)]="user.name">
在todo組件的模板代碼中,我們還用到了兩個指令*ngIf
和*ngFor
,它們是Angular的內置指令,用法根據上面的例子應該就能看懂。最後還有一個語法是管道,操作符是:”|”,這個跟其他框架中所謂的“過濾器”的概念很像。它將操作符左側的表達式的值作爲右側函數的輸入,最終整個表達式的值由右側函數的返回值決定。管道還能串聯。
以上就是模板的基本語法,只是簡單的說明,具體請參考文檔。
這樣todo組件的模板就寫好了,最後我們可以在todo.component.css
中加些樣式。比如:
.hint{
color: #999999;
}
.done{
text-decoration: line-through;
}
li{
line-height: 2;
}
以上便是整個TODO應用的編寫過程,你跑出來了嗎?目前程序還有一個小缺陷,就是我們回車,輸入的內容被加入todo列表後,輸入框應該被清空,這個就交給你去完成了。