Controller控制器
控制器的定義
控制器的作用是通過附加模型和和方法來為其擴(kuò)大作用域趴泌,為了隨后在視圖層能夠訪問的到。AngularJS中的控制器就是在html文件中AngularJS程序遇到ng-controller指令的時(shí)候生成的構(gòu)造函數(shù)怠噪。我們將會在下一章中探索AngularJS的作用域。正如我在第一章中提到的杜跷,作用域scope就是控制器和視圖層之間的膠水傍念,所以控制器能夠添加屬性,視圖層能夠訪問到這些屬性葛闷。
為控制器附加屬性和方法
下一步憋槐,我們寫一個(gè)控制器,用來通過一個(gè)作用域傳遞現(xiàn)在的時(shí)間到視圖層淑趾。
controller
<pre><code>
angular.module('myAPP',[])
.controller('GreetingController',function($scope){
$scope.now=new Data();//把時(shí)間模型附加到作用域上
$scope.greeting='Hello';
})
</code></pre>
通過在angular.module()上調(diào)用controller()來注冊一個(gè)控制器阳仔。這個(gè)控制器需要兩個(gè)參數(shù):第一個(gè)參數(shù)是這個(gè)控制器的名字,第二個(gè)是當(dāng)在html中執(zhí)行到ng-controller指令時(shí)被調(diào)用的實(shí)例方法扣泊。能夠忽略這個(gè)實(shí)例方法中的作用域$scope參數(shù)嗎近范?當(dāng)你聲明這個(gè)$scope作為一個(gè)控制器實(shí)例的參數(shù),你就表明你的控制器是依賴于這個(gè)$scope對象的延蟹。這個(gè)$scope的參數(shù)有特殊意義评矩。AngularJS根據(jù)實(shí)例方法參數(shù)的名字來推斷這個(gè)控制器的依賴。在這個(gè)例子中阱飘,當(dāng)AngularJS在HTML文件中找到ng-controller='GreetingController'這個(gè)指令時(shí)斥杜,AngularJs會為控制器創(chuàng)建一個(gè)新的作用域,當(dāng)實(shí)例化這個(gè)方法時(shí)沥匈,AngularJS會傳遞這個(gè)作用域作為一個(gè)參數(shù)到這個(gè)實(shí)例方法果录。這叫做依賴注入,是AngularJS的核心咐熙。我們將會在后面的章節(jié)中討論依賴注入的話題弱恒。
多個(gè)依賴
能夠?yàn)榭刂破髟O(shè)置多個(gè)依賴關(guān)系,并且能夠聲明這些依賴作為參數(shù)傳遞到構(gòu)造方法中棋恼。為了增加$scope返弹,每個(gè)AngularJS應(yīng)用都有一個(gè)一個(gè)$rootScope锈玉。我們假設(shè)你有一個(gè)普通的服務(wù)用來通過XHR連接后端。你可以聲明這兩個(gè)服務(wù)依賴义起,像如下一樣:
<pre><code>
angular.module('myApp',[])
.controller(ControllerWithDependency',function($rootScope,customService){
//在這里使用依賴
});
</code></pre>
當(dāng)實(shí)例化控制器的時(shí)候拉背,AngularJS會讀取參數(shù)列表,并且從這些名字中可以指定哪些需要被依賴注入默终。你也需要注意到AngularJS自己自帶的服務(wù)前綴是以$為命名約定的椅棺。所以你不應(yīng)該以$作為你服務(wù)的前綴。
<!DOCTYPE html>
<html ng-app="myApp">
<head >
<script type='text/javascript' src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js" ></script><
<script src="app.js"></script>
</head>
<body ng-controller="GreetingController">
{{greeting}} User! The current date/time is <span>{{now | date:'medium'}}</span>
</body>
</html>
在這里我們把a(bǔ)ngular的ng-controller指令加入到HTML中齐蔽,這意味著在<body></body>標(biāo)簽之間的所有內(nèi)容均在控制器的作用域下两疚。每次ng-controller檢測,AngularJS為這個(gè)特殊的控制器和實(shí)例創(chuàng)造一個(gè)新的作用域含滴。所以當(dāng)ng-controller="GreetingController"遇到這個(gè)構(gòu)造方法GreetingController運(yùn)行的時(shí)候诱渤,為作用域下的兩個(gè)模型:greeting和now賦值。在視圖中我們可以通過表達(dá)式{{}}來獲取值谈况。當(dāng)我們寫{{greeting}}AngularJS會用這個(gè)已經(jīng)存在greeting屬性的值來替代它勺美。對于now模型來說也是如此。無論在{{}}里寫的是什么碑韵,都要能和scope對應(yīng)上赡茸。
當(dāng)你在瀏覽器里運(yùn)行HTML的時(shí)候你應(yīng)該看到如下
Hello, User! The current date/time is <current date & time here>.
添加邏輯到控制器
除了操作用戶輸入和為控制器賦值,一個(gè)控制器也為$scope添加方法祝闻,這些方法表現(xiàn)某種類型的邏輯并且和服務(wù)一起封裝應(yīng)用的業(yè)務(wù)數(shù)據(jù)占卧。
為了明白這些,讓我們?yōu)?scope添加一個(gè)方法治筒,并且在一個(gè)隨機(jī)的語言中返回greeting的值。
下面是如何定義控制器:
angular.module('myApp',[])
.controller('GreetingController',function($scope){
$scope.now=new Date();
$scope.helloMessages = ['Hello', 'Bonjour', 'Hola', 'Ciao',Hallo'];
$scope.greeting = $scope.helloMessages[0];
$scope.getRandomHelloMessage=function(){
$scope.greeting=$scope.helloMessages[parseInt(Math.random()*$scope.helloMessages.length)]
}
})
這里我們把helloMessages字符數(shù)組數(shù)組添加到$scope當(dāng)前作用域上舷蒲,表示hello五種語言的表達(dá)耸袜。我們也為當(dāng)前作用域$scope上附加了getRandomHelloMessage()方法隨機(jī)選擇一個(gè)信息并且賦值給作用域下greeting模型。作為數(shù)據(jù)綁定的結(jié)果牲平,當(dāng)$scope.greeting被更新堤框,在視圖層表達(dá)式{{greeting}}也會被改變。
相應(yīng)的視圖是
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script type='text/javascript' src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="GreetingController">
{{greeting}} User! The current date/time is <span>{{now | date:'medium'}}</span>
<br/>
<button ng-click="getRandomHelloMessage()">Random Hello Message</button>
</body>
</html>
這里我們增加了一個(gè)HTML按鈕用來通過調(diào)用在控制器的作用域上getRandomHelloMessage()方法來響應(yīng)點(diǎn)擊事件纵柿。反過來蜈抓,這個(gè)方法改變greeting模型的值。所以這個(gè)變化被反射到視圖層昂儒。
誠然沟使,這個(gè)例子是很簡單的。
但是我們學(xué)習(xí)到了基本的控制器和如何使用它渊跋,現(xiàn)在我們來討論腊嗡,控制器不該做什么着倾。
- 不要進(jìn)行DOM操作,DOM操作應(yīng)該在指令中進(jìn)行燕少。
- 不要在控制器中格式化模型的值卡者,過濾器的作用就在此,我們已經(jīng)見過內(nèi)置過濾器的操作客们。
- 不要在控制器中寫重復(fù)的代碼崇决。而是要封裝到服務(wù)中,例如你要在多個(gè)地方從服務(wù)中獲取數(shù)據(jù)底挫。所以你與其在控制器中重復(fù)代碼恒傻,不如把代碼封裝在服務(wù)中,當(dāng)需要的時(shí)候注入到控制器中凄敢。當(dāng)所有的控制器線程處理完用戶輸入后碌冶,為當(dāng)前作用域$scope設(shè)置屬性和方法,并且和服務(wù)進(jìn)行交互來表現(xiàn)業(yè)務(wù)邏輯涝缝。
控制器應(yīng)該做:
+通過為控制器附加模型來設(shè)定$scope的初始狀態(tài)扑庞,
+為作用域附加方法用來處理任務(wù)。
為控制器附加實(shí)例方法和屬性
盡管控制器經(jīng)常為作用域設(shè)置方法和屬性拒逮,你也可以為控制器創(chuàng)建一個(gè)實(shí)例方法和屬性罐氨。記住,AngularJS實(shí)例化你的控制器通過調(diào)用你提供的構(gòu)造方法滩援。這意味著你有創(chuàng)建實(shí)例屬性和實(shí)例方法的自由栅隐。讓我們定義以前的控制器,用實(shí)例屬性代替作用域模型玩徊。
angular.module('myApp',[])
.controller('GreetingController',function($scope){
this.now=new date();
this.helloMessage=['Hello', 'Bonjour', 'Ola', 'Ciao', 'Hallo'];
this.greeting = this.helloMessages[0];
this.getRandomHelloMessage = function() {this.greeting = this.helloMessages[parseInt((Math.random()*this.helloMessages.length))];
}
})
在視圖層有一個(gè)小調(diào)整租悄。
<!DOCTYPE html>
<html ng-app="myApp">
<head><br>
<script type='text/javascript'src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
<script src="app.js">>/script>
</head>
<body ng-controller="GreetingController as greetingController">
{{greetingController.greeting}} User! The current date/time is<span>{{greetingController.now | date: 'medium'}}</span>
<br/>
<button ng-click="greetingController.getRandomHelloMessage()">Random Hello Message</button>
</body >
</html >
這個(gè)GreetingController作為控制器完成了變換。這個(gè)作為關(guān)鍵字給GreetingController的實(shí)例設(shè)置作用域恩袱。所以作為關(guān)鍵字把控制器暴露給視圖層泣棋。并且作為結(jié)果,我們能夠通過這個(gè)reference參數(shù)獲取這個(gè)實(shí)例的變量和方法畔塔。
不是所有的開發(fā)者潭辈,支持這個(gè)實(shí)現(xiàn)方法。讓我們看看作為關(guān)鍵字什么是好的什么是壞的澈吨。
缺點(diǎn)
+在許多案例中暴露整個(gè)控制器實(shí)例不是一個(gè)好的方法把敢。這個(gè)作用域?qū)ο笄宄拇嬖谟诳刂破骱鸵晥D層之間。
+這個(gè)實(shí)現(xiàn)方法不是主流的谅辣,并且也需要更多的代碼修赞。
優(yōu)點(diǎn)
+當(dāng)控制器是嵌套的,并且內(nèi)層和外層控制器作用域?qū)τ谀P陀邢嗤拿稚=祝鳛殛P(guān)鍵字就派上用場了榔组。
<div ng-controller='OuterController as outer'>
<div ng-controller='InnerController as inner'>
Outer={{outer.someModel}} and inner={{inner.someModel}}
在控制器中使用依賴注入
我們已經(jīng)見過如何把依賴注入到控制器中熙尉,AngularJS從構(gòu)造函數(shù)的參數(shù)的名字來推斷控制器的依賴,當(dāng)部署代碼的時(shí)候壓縮Javascipt代碼搓扯,這個(gè)參數(shù)的名字將會縮短检痰,結(jié)果AngularJS將會無法識別出依賴。這種情況锨推,你有兩個(gè)選擇來理清依賴當(dāng)壓縮代碼的時(shí)候铅歼,讓我們來檢查這兩種方法。
function DemoController($rootScope,$scope,$http){
}
DemoController.$inject=['$rootScope','$scope','$http'];
angular.module('myApp',[]) .controller('DemoController',DemoController)
這個(gè)$inject屬性表明控制器到構(gòu)造函數(shù)參數(shù)之間的依賴關(guān)系换可。
第二個(gè)選擇在開發(fā)者中更受歡迎椎椰。你可以在angular.module()中第二個(gè)參數(shù)傳遞一個(gè)數(shù)組,而不是傳遞一個(gè)構(gòu)造函數(shù)沾鳄。這個(gè)數(shù)組存儲依賴的名字慨飘,數(shù)組中的最后一項(xiàng)就是控制器構(gòu)造函數(shù)的方法。這里我們?nèi)绾巫觥?/p>
angular.module('myApp',[])
.controller('DemoController',['$rootScope','$scope','$htp',function($rootScope,$scope,$http){
}])
你會喜歡上面簡單的代碼译荞,注入到構(gòu)造函數(shù)中依賴關(guān)系是在數(shù)組中按順序聲明的瓤的。
這些方法達(dá)到同樣的目的,第一種實(shí)現(xiàn)方法代碼有點(diǎn)長吞歼,相比第二種在行內(nèi)并且代碼要短圈膏。第二種實(shí)現(xiàn)方法的情況下,參數(shù)列表緊密的在一起篙骡。結(jié)果稽坤,如果你需要改變什么,你需要一個(gè)單獨(dú)的位置來編輯糯俗。你可以在兩行把參數(shù)列表對齊尿褪,這樣你就可以看到他們是不是相互匹配。
雙向數(shù)據(jù)綁定方法概述
現(xiàn)在你已經(jīng)明白了模塊和控制器得湘,是時(shí)候往前走一點(diǎn)杖玲,介紹AngularJS最基本的特性之一雙向數(shù)據(jù)綁定。盡管本節(jié)忽刽,給了一個(gè)雙向數(shù)據(jù)綁定的概述天揖,重要的是在第三章會講述的夺欲。
什么是數(shù)據(jù)綁定
數(shù)據(jù)綁定是在視圖層和模型層自動同步數(shù)據(jù)跪帝。當(dāng)我們說雙向的時(shí)候,那意味著同步方式兩種都進(jìn)行些阅。在angular中我們創(chuàng)建模型并且為其賦予scope對象伞剑。
然后我們把UI組件和這些模型綁定在一起。這就建立了雙向綁定在視圖層組件和模型數(shù)據(jù)之間市埋。當(dāng)這個(gè)模型數(shù)值變化的時(shí)候黎泣,這個(gè)視圖層來更新自己反應(yīng)變化恕刘。另一方面,當(dāng)視圖層變化的時(shí)候抒倚,模型層也會更新自己褐着。
如果你有Flex或者Actionscript背景,可能你已經(jīng)每天使用這種綁定方式托呕,但是這種特性在HTML中還是全新的含蓉,讓我們把模型層的數(shù)據(jù)當(dāng)作數(shù)據(jù)的唯一來源,反過來项郊,把你從操作DOM結(jié)構(gòu)中解放出來馅扣。AngularJS保證你的視圖層總是和最新的模型層數(shù)據(jù)保持更新。所以你僅僅改變這個(gè)數(shù)據(jù)着降,你的視圖層會自動更新沒有副作用的差油。換種說法,沒必要使用innerHTML任洞。但是如果你愿意蓄喇,你仍然能夠手動的更新DOM。
AngularJS中的雙向綁定
接下來的例子侈咕,簡單的介紹了雙向綁定公罕。簡單起見,繞過控制器耀销,集中到控制器
如果你運(yùn)行上面的代碼楼眷,你會看到當(dāng)input輸入框中的值發(fā)生改變的時(shí)候,這個(gè)div中的值熊尉,也會發(fā)生變化罐柳。讓我們一步一步看看發(fā)生了什么:
1、第一狰住,這個(gè)ng-app指令啟動這個(gè)應(yīng)用张吉。我們沒有模塊,所以我們僅僅寫下ng-app沒有指定值催植。有一點(diǎn)需要注意的是肮蛹,當(dāng)AngularJS遇到ng-app,它會給HTML產(chǎn)生rootscope根作用域创南。這個(gè)作用域scope是我們存儲模型數(shù)據(jù)的地方這樣才能被視圖層獲取伦忠。
2、接下來稿辙,ng-init指令產(chǎn)生了一個(gè)名為name的模型昆码,把它保存在rootscope根作用域。這個(gè)模型被初始化為AngularJS的值。
3赋咽、我們已經(jīng)把ng-modal指令附加到input元素上旧噪。這是基本的雙向數(shù)據(jù)綁定。當(dāng)你在input輸入框輸入東西的時(shí)候脓匿,這個(gè)作用域自動更新淘钟。所以,這樣你就不必要手動為input輸入框?qū)懸粋€(gè)keyup事件句柄來更新數(shù)據(jù)陪毡。
4日月、 {{name}}被綁定在模型層數(shù)據(jù)上作為一個(gè)表達(dá)式被視圖層單向知道。這就是雙向數(shù)據(jù)綁定的第二個(gè)方面缤骨,表達(dá)式監(jiān)聽著作用域模型值爱咬,并且更新這個(gè)DOM,當(dāng)值變化的時(shí)候绊起。
5精拟、所以當(dāng)我們在input輸入框輸入內(nèi)容的時(shí)候,作用域模型層數(shù)據(jù)發(fā)生變化虱歪,在視圖層中的表達(dá)式{{name}}蜂绎,當(dāng)name屬性發(fā)生變化會自動更新。
做一些酷酷的事情
當(dāng)事情變得復(fù)雜一點(diǎn)點(diǎn)時(shí)候數(shù)據(jù)綁定的能力就變得明顯了笋鄙。所以师枣,讓我們干點(diǎn)酷酷的事情,比如說萧落,你已經(jīng)被賦予了以下的任務(wù)践美。
1.為使用者提供一個(gè)input輸入框用來輸入facebook賬號。
2.只要使用者輸入完成找岖,顯示相應(yīng)的facebook資料圖片陨倡。
唯一的約束是讓代碼的數(shù)量行數(shù)最小化,這個(gè)第一個(gè)選擇是使用純javascript代碼(或者使用jquery)许布。所以兴革,打開你的編輯器,寫下這些代碼為了這些功能蜜唾,然后回到這里杂曲。但是我給你一個(gè)線索,這個(gè)接下來的URL袁余,返回一個(gè)facebook的資料圖片擎勘。Facebook ID: https://graph.facebook.com/[id here]/picture?type=normal.
純javascript代碼。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The Plain JS Way</title>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded',function(e){
doucument.getElementById('fbID').addEventListener('keyup',function(event){
var fbID=document.getElementById('fbID').value;
var pictureURL='https://graph.facebook.com/' + fbID +'/picture?type=normal';
document.getElementById('profilePic').src=pictureURL;
})
})
</script>
</head>
<body>
<input type="text" id="fbID" />
<br/>
<span><img src="" title="fb image" id="profilePic"/></span>
</body>
</html>
在AngularJS中使用雙向綁定
<!doctype html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<title>Two way data binding</title>
</head>
<body ng-init="fbID='sandeep.panda92'">
<input type="text" ng-modal="fbID"><br/>
<span>![](https://graph.facebook.com/{{fbID}}picture?type=normal)</span>
<script src="lib/angular/angular.js"></script>
</body>
</html>
當(dāng)你看到angularJS的版本的時(shí)候會有一點(diǎn)混亂泌霍。那這是如何造成的货抄。數(shù)據(jù)雙向綁定的威力剛剛顯示出來。我們先來了解場景背后的內(nèi)容:
1.當(dāng)我們在input輸入的時(shí)候朱转,多虧了AngularJS這個(gè)fbID模型就會更新蟹地。
2.當(dāng)我們在視圖中使用表達(dá)式{{fbID}},當(dāng)fbID模型的值會更新它自己藤为,這樣會顯示出新圖標(biāo)怪与。
關(guān)于angularJS實(shí)現(xiàn)最重要的事情是代碼的規(guī)模是極具下降。甚至實(shí)現(xiàn)這個(gè)編碼功能不使用javascript缅疟。這就是聲明綁定的能力分别。你可以通過在視圖層通過作用域綁定不同的UI組件來實(shí)現(xiàn)一個(gè)特殊的場景。如果你的視圖層一些需要更新存淫,你需要更新模型層耘斩。它確保你表達(dá)什么數(shù)據(jù)被更新了對于一個(gè)特殊的組件。如果其他的開發(fā)者看到了你的代碼桅咆,可以推斷接下來的應(yīng)用括授。
總結(jié)一下,雙向綁定的特性:
1岩饼、在視圖層和模型層兩個(gè)方向荚虚,同步的保持?jǐn)?shù)據(jù)更新。
2籍茧、提供一個(gè)強(qiáng)大的陳述性語法來展現(xiàn)應(yīng)用來實(shí)現(xiàn)什么功能版述。
3、把我們從繁瑣的不能測試的DOM操作中解放出來寞冯。
4渴析、顯著的縮減代碼規(guī)模。
介紹我們的應(yīng)用DEMO
學(xué)習(xí)一個(gè)新技術(shù)而不練習(xí)吮龄,這不是忍者的方式檬某。我要確信你每一章節(jié)正確的學(xué)習(xí)方式。練習(xí)能夠給你在現(xiàn)實(shí)世界中操作大型angular項(xiàng)目的信心螟蝙。所以我們要創(chuàng)建一個(gè)示例應(yīng)用恢恼,并且加入一些我們計(jì)劃的特性。讓我們看一看這個(gè)簡單的示例應(yīng)用胰默。
一個(gè)簡單的單頁面博客
我們將要為所有的單頁面愛好者開發(fā)一個(gè)單頁面應(yīng)用解決方案场斑。想象一下你有一個(gè)高能力的博客系統(tǒng),由單頁面系統(tǒng)控制加載牵署。下面是一個(gè)關(guān)于這個(gè)應(yīng)用的概括:
(1)博客的首頁展現(xiàn)所有的博客帖子漏隐,包括帖子的名稱、作者和發(fā)布時(shí)間奴迅。
(2)當(dāng)一個(gè)單頁面博客被點(diǎn)擊的時(shí)候青责,這個(gè)實(shí)際的條目內(nèi)容會被異步的加載進(jìn)這個(gè)頁面挺据。不會觸發(fā)整個(gè)頁面重載。
(3)有一個(gè)管理員頁面脖隶,管理員可以進(jìn)行增刪改查操作扁耐。
所以,這是這個(gè)app基礎(chǔ)的特性产阱。隨著我們往前看書婉称,我們將會往app中添加一些激動人心的功能,比如一個(gè)完整的評論系統(tǒng)构蹬、認(rèn)證和授權(quán)王暗、facebook或者twitter登錄界面。
準(zhǔn)備開始
剛開始庄敛,我們要以在前面章節(jié)中討論過的特征模塊化我們的代碼俗壹。我們已經(jīng)說過如何把AngularJS。seed項(xiàng)目分解成介紹的這種模式藻烤。我們需要做的是從AngularJS seed項(xiàng)目中分解出新項(xiàng)目策肝,并且創(chuàng)造一個(gè)簡略的不同項(xiàng)目結(jié)構(gòu)。只有這個(gè)app下的目錄結(jié)構(gòu)需要修改隐绵,其他的就不變了之众。我已經(jīng)分享過這個(gè)目錄結(jié)構(gòu),你可以直接下載后開始依许」缀蹋快速的看一下圖2.1的屏幕截圖,來看看什么改變了峭跳。
我已經(jīng)為這個(gè)app增加了一個(gè)叫做post的模塊膘婶,現(xiàn)在還沒有添加內(nèi)容。當(dāng)你下載完這個(gè)zip文件蛀醉,你可以查看這個(gè)文件結(jié)構(gòu)和檢查模塊悬襟。我們將在第五章重新開始開發(fā)這個(gè)app。
結(jié)論
每一個(gè)忍者水平的開發(fā)者喜歡更強(qiáng)大的能力≌螅現(xiàn)在你得到了三個(gè)而不是一個(gè)能力脊岳,模塊、控制器垛玻、數(shù)據(jù)綁定割捅。我想他們會幫助你完成這個(gè)angularJS旅程。接下來會帶給你一些新的內(nèi)容帚桩。但是同時(shí)你可以嘗試一些雙向綁定的技巧亿驾,給你的同事留下印象。