寫(xiě)在前面:本文適合AngularJS新手,老手略過(guò)。
AngularJS是一款非常強(qiáng)大的前端MVC框架。同時(shí),它也引入了相當(dāng)多的概念,這些概念我們可能不是太熟悉。
下面我們逐個(gè)來(lái)看這些概念,研究一下為什么它們會(huì)像當(dāng)初設(shè)計(jì)的那樣強(qiáng)大,同時(shí)研究一下為什么我們要以那樣的方式去使用它們。
本文系轉(zhuǎn)載,點(diǎn)我看原文,版權(quán)歸原作者所有。
如果你已經(jīng)使用過(guò)AngularJS,你可能已經(jīng)遇到過(guò)Service
這個(gè)概念了,簡(jiǎn)而言之,Service
就是 單例對(duì)象 在AngularJS中的一個(gè)別名。這些小東西(指單例對(duì)象)會(huì)被經(jīng)常傳來(lái)傳去,保證你每次訪問(wèn)到的都是同一個(gè)實(shí)例。這一點(diǎn)和工廠模式不同。
基于這種思想,單例對(duì)象讓我們可以實(shí)現(xiàn)一些相當(dāng)酷的功能,它可以讓很多controller
和directive
訪問(wèn)內(nèi)部的數(shù)值。
我們來(lái)看一個(gè)非常常見(jiàn)的問(wèn)題,那就是在應(yīng)用中的不同代碼塊之間如何共享數(shù)據(jù)?
我們首先創(chuàng)建一個(gè)module(模塊),本文中的所有代碼都會(huì)用到這個(gè)module。
// 創(chuàng)建一個(gè)angularjs的module
var module = angular.module('my.new.module', []); // 這里的第二個(gè)空數(shù)組參數(shù)一定要有
下一步,我們來(lái)創(chuàng)建一個(gè)新的service(服務(wù))。假設(shè)我們上面的這個(gè)module是用來(lái)管理圖書(shū)的。所以,這里我們來(lái)創(chuàng)建一個(gè)Book Service,然后把一個(gè)JSON對(duì)象數(shù)組添加到這個(gè)service中。
module.service('Book', [
'$rootScope',
function($rootScope) {
var service = {
books: [{
title: 'Magician',
author: 'Raymond E, Feist'
}, {
title: 'The Hobbit',
author: 'J.R.R Tolkien'
}],
addBook: function(book) {
service.books.push(book);
$rootScope.$broadcast('books.update');
}
};
return service;
}
]);
這個(gè)一個(gè)非常簡(jiǎn)單的service(有時(shí)候這樣的service其實(shí)就夠用了)。我們這里正在做的事情就是在管理一個(gè)book數(shù)組,同時(shí)還帶有一個(gè)addBook
方法,在有需要的時(shí)候可以添加更多的書(shū)籍。addBook
方法還會(huì)在application上廣播一個(gè)事件,告訴所有正在我們服務(wù)Book
的人,數(shù)組已經(jīng)被更新了,從而讓它們自己也做一些刷新操縱。
現(xiàn)在,我們要做的就是把這個(gè)service傳遞給各種controller、directive、filter,或者其他任何需要它的東西,然后它們就可以訪問(wèn)service中提供的這些方法和屬性了。
下面我們聲明一個(gè)controller:
module.controller('books.list', [
'$scope',
'Book',
function($socpe, Book) {
// books.update事件監(jiān)聽(tīng)
$scope.$on('books.update', function(event) {
$scope.books = Book.books;
$scope.$apply();
});
$scope.books = Book.books;
}
]);
同樣非常簡(jiǎn)單。我們上面所做的就是為了我們的module
創(chuàng)建了一個(gè)新的controller
。在創(chuàng)建的時(shí)候把$scope.provider
和我們自己的Book service
傳遞給了它。能明白我們?cè)诟陕飭??我們把前面?chuàng)建的Book service
中的books
數(shù)組賦給了controller
內(nèi)部的局部$scope對(duì)象。很酷,對(duì)吧?
好,這里的核心問(wèn)題是什么呢?我們節(jié)省了一些時(shí)間,并且在controller
上創(chuàng)建了一個(gè)數(shù)組。對(duì),我們確實(shí)這樣做了。這樣做確實(shí)也為我們節(jié)約了一點(diǎn)時(shí)間,但是如果我們要在其它地方處理這些書(shū)籍信息應(yīng)該怎么辦呢?通過(guò)$scope
來(lái)維護(hù)數(shù)據(jù)是非常粗暴的一種方式。由于其它controller
、directive
、model
的影響,$scope
很容易就會(huì)崩潰或者變臟。它很快就會(huì)變成一團(tuán)亂麻。通過(guò)一種集中的途徑(在這里 就是service)來(lái)管理所有書(shū)籍?dāng)?shù)據(jù),然后通過(guò)某種方式來(lái)請(qǐng)求修改它,這樣不僅僅會(huì)更加清晰,同時(shí)當(dāng)應(yīng)用的體積不斷增大的時(shí)候也更加容易管理。最后,它還可以讓你的代碼保持模塊化(這也是Angular很擅長(zhǎng)的一件事情)。一旦你在其它項(xiàng)目中需要用到這個(gè)service
,你沒(méi)有必要在$scope
、 controller
、filter
等等東西里面到處去查找相關(guān)的代碼,因?yàn)?strong>所有東西都在service
里面!
好。那么我們什么時(shí)候應(yīng)該使用service呢?答案是:無(wú)論何時(shí),當(dāng)我們需要在不同的域中共享數(shù)據(jù)的時(shí)候。另外,多虧了Angular的 依賴注入系統(tǒng) ,實(shí)現(xiàn)這一點(diǎn)是很容易并且很清晰的。
我們?cè)賮?lái)看控制器!
除非你曾經(jīng)使用過(guò)前端MVC,否則從服務(wù)端MVC的思維模式轉(zhuǎn)向客戶端MVC的思維模式就如同一次腦筋急轉(zhuǎn)彎。為什么會(huì)這樣呢?
這是因?yàn)椋m然在前端開(kāi)發(fā)中controller
實(shí)現(xiàn)了非常類(lèi)似的功能,但是它同時(shí)還會(huì)實(shí)現(xiàn)一些與服務(wù)端controller非常不同的功能。在 Angular中,controller
自身并不會(huì)處理request,除非它是用來(lái)處理路由(route)的(很多人把這種方式叫做創(chuàng)建route controller
,路由控制器),更明確地說(shuō),尤其是你的應(yīng)用里面那些作為界面的一部分的controller,它們只會(huì)管理非常小的一段代碼。
controller應(yīng)該純粹地用來(lái)把service、依賴關(guān)系、以及其它對(duì)象串聯(lián)到一起,然后通過(guò)$scope把它們關(guān)聯(lián)到view上。如果在你的視圖里面需要處理復(fù)雜的業(yè)務(wù)邏輯,那么把它們放到controller里面也是一個(gè)非常不錯(cuò)的選擇?;氐轿覀兦懊娴倪@個(gè)books例子,我實(shí)際上并沒(méi)有什么東西需要添加到controller里面。
但是如果我要add一本書(shū)籍應(yīng)該怎么辦呢?我應(yīng)該在controller上面新增一個(gè)方法來(lái)處理這件事情嗎? 不,原因在下面解釋。因?yàn)樗荄OM交互/操作的一部分。所以請(qǐng)把它放到directive(指令)里面。怎么做呢?很高興你能問(wèn)出這個(gè)問(wèn)題。
到目前為止,在我們所編寫(xiě)的大量AngularJS應(yīng)用中,應(yīng)用中最主要的復(fù)雜部分都在directive(指令)中。有一個(gè)強(qiáng)大的工具可以用來(lái)操作和修改DOM,它也是我們這里需要討論的內(nèi)容。我們來(lái)提供一個(gè)按鈕,用戶通過(guò)它可以向service里面添加一本圖書(shū),以這一功能來(lái)結(jié)束此文。
一個(gè)常見(jiàn)的反模式(即反面教材)是在controller里面添加DOM交互代碼。Angular對(duì)directive
的定義是一段代碼片段,你可以用它來(lái)操作DOM,但是我覺(jué)得directive
也是進(jìn)行用戶交互的很好選擇。我們來(lái)擴(kuò)展前面的例子,為用戶提供一個(gè)按鈕,通過(guò)這個(gè)按鈕可以向service
里面添加一本書(shū)籍。
module.directive("addBookButton", [
'Book',
function(Book) {
return {
restrict: "A",
link: function(scope, element, attrs) {
element.bind( "click", function() {
Book.addBook({
title: "Star Wars",
author: "George Lucas"
});
});
}
};
}
]);
很簡(jiǎn)單的東西。我們創(chuàng)建了一個(gè)指令,它的核心目的是簡(jiǎn)單地向books列表中添加一本書(shū)籍,books已經(jīng)注冊(cè)在了我們的Book服務(wù)中。我們來(lái)把這個(gè)指令應(yīng)用到我們的視圖中。
<button add-book-button>Add book</button>
如你所見(jiàn),我們僅僅把指令當(dāng)作一個(gè)元素屬性來(lái)使用。每次點(diǎn)擊這個(gè)按鈕的時(shí)候,它都會(huì)把《Star Wars》(《星球大戰(zhàn)》)這本書(shū)添加到我們的Book service
中去。簡(jiǎn)單、輕松、模塊化,并且易復(fù)用。
好了,我們?yōu)槭裁床恢苯釉诳刂破魃厦嫣砑右粋€(gè)addBook之類(lèi)的方法呢,比如說(shuō)就像下面這樣:
$scope.addBook = function() {
Book.addBook({
title: "Star Wars",
author: "George Lucas"
});
};
這樣我們也能獲得同樣的結(jié)果,對(duì)吧?是的,確實(shí)如此,但是這樣做會(huì)帶來(lái)一個(gè)重大的問(wèn)題。
一旦我需要在其它地方添加書(shū)籍,我必須拷貝這份代碼(非常un-DRY!,DRY—Dont Repeat Yourself,貌似是Ruby所倡導(dǎo)的一個(gè)重要的編碼原則。),或者進(jìn)行重構(gòu)(重構(gòu)本身并不是什么不好的的事情)。通過(guò)直接構(gòu)建一個(gè)指令的方式,我們以后就沒(méi)有必要擔(dān)心這種事情了,同時(shí)下次再需要實(shí)現(xiàn)相同功能的時(shí)候完全不需要花任何時(shí)間。通過(guò)構(gòu)建指令的方式來(lái)進(jìn)行DOM交互和修改,隨著業(yè)務(wù)需求的不斷介入,我們就可以立即騰出手來(lái)處理復(fù)雜性不斷增加的應(yīng)用了。這是相當(dāng)不錯(cuò)的一件事情,因?yàn)樗WC了我們可以更少地和自己的實(shí)現(xiàn)打架,并且可以一直編寫(xiě)DRYer code。
Angular的模塊依賴哲學(xué)無(wú)疑讓它成為了一款非同凡響的框架。它讓我們能夠以這樣一種方式來(lái)編寫(xiě)我們的前端代碼:我們不會(huì)干翻自己,也不會(huì)干翻框架—這可能是它最強(qiáng)大的力量。
希望我已經(jīng)充分說(shuō)明了你應(yīng)該在何時(shí)何地使用這幾個(gè)Angular概念,從而能夠更好地編寫(xiě)你自己的代碼。
更多建議: