控制器在AngularJS中的作用是增強(qiáng)視圖。
AngularJS中的控制器是一個(gè)函數(shù),用來(lái)向視圖的作用域中添加額外的功能。我們用它來(lái)給作用域?qū)ο笤O(shè)置初始狀態(tài),并添加自定義行為。
當(dāng)我們?cè)陧?yè)面上創(chuàng)建一個(gè)新的控制器時(shí),AngularJS會(huì)生成并傳遞一個(gè)新的$scope給這個(gè)控制器??梢栽谶@個(gè)控制器里初始化scope。由于AngularJS會(huì)負(fù)責(zé)處理控制器的實(shí)例化過(guò)程,我們只需編寫(xiě)構(gòu)造函數(shù)即可。下面的例子展示了控制器初始化:
function FirstController($scope) {$scope.message = "hello";}
細(xì)心的讀者會(huì)發(fā)現(xiàn),我們是在全局作用域中創(chuàng)建的這個(gè)函數(shù)。這樣做并不合適,因?yàn)闀?huì)污染全局命名空間。更合理的方式是創(chuàng)建一個(gè)模塊,然后在模塊中創(chuàng)建控制器,如下所示:
var app = angular.module('app', []);app.controller('FirstController', function($scope) {$scope.message = "hello";});
只需創(chuàng)建控制器作用域中的函數(shù),就能創(chuàng)建可以在視圖中使用的自定義操作。很幸運(yùn),AngularJS允許我們?cè)谝晥D中像調(diào)用普通數(shù)據(jù)一樣調(diào)用$scope上的函數(shù)。
用內(nèi)置指令ng-click可以將按鈕、鏈接等其他任何DOM元素同點(diǎn)擊事件進(jìn)行綁定。ng-click指令將瀏覽器中的mouseup事件,同設(shè)置在DOM元素上的事件處理程序綁定在一起(例如,當(dāng)瀏覽器在某個(gè)DOM元素上觸發(fā)了點(diǎn)擊事件,函數(shù)就會(huì)被調(diào)用)。和前面的例子類(lèi)似,綁定看起來(lái)是這樣的:
<div ng-controller="FirstController"><h4>The simplest adding machine ever</h4><button ng-click="add(1)" class="button">Add</button><a ng-click="subtract(1)" class="button alert">Subtract</a><h4>Current count: {{ counter }}</h4></div>
按鈕和鏈接都被綁定在了內(nèi)部$scope的一個(gè)操作上,當(dāng)點(diǎn)擊任何一個(gè)元素時(shí)AngularJS都會(huì)調(diào)用相應(yīng)的方法。注意,當(dāng)設(shè)置調(diào)用哪個(gè)函數(shù)時(shí),會(huì)同時(shí)用括號(hào)傳遞一個(gè)參數(shù)(add(1))。
下面給FirstController添加一個(gè)操作:
app.controller('FirstController', function($scope) {$scope.counter = 0;$scope.add = function(amount) { $scope.counter += amount; };$scope.subtract = function(amount) { $scope.counter -= amount; };});
控制器可以將與一個(gè)獨(dú)立視圖相關(guān)的業(yè)務(wù)邏輯封裝在一個(gè)獨(dú)立的容器中。盡可能地精簡(jiǎn)控制器是很好的做法。作為AngularJS開(kāi)發(fā)者,使用依賴(lài)注入來(lái)訪(fǎng)問(wèn)服務(wù)可以實(shí)現(xiàn)這個(gè)目的。
AngularJS同其他JavaScript框架最主要的一個(gè)區(qū)別就是,控制器并不適合用來(lái)執(zhí)行DOM操作、格式化或數(shù)據(jù)操作,以及除存儲(chǔ)數(shù)據(jù)模型之外的狀態(tài)維護(hù)操作。它只是視圖和$scope之間的橋梁。
默認(rèn)情況下,AngularJS在當(dāng)前作用域中無(wú)法找到某個(gè)屬性時(shí),便會(huì)在父級(jí)作用域中進(jìn)行查找。如果AngularJS找不到對(duì)應(yīng)的屬性,會(huì)順著父級(jí)作用域一直向上尋找,直到抵達(dá)$rootScope為止。如果在rootScope中也找不到,程序會(huì)繼續(xù)運(yùn)行,但視圖無(wú)法更新。
通過(guò)例子來(lái)看一下這個(gè)行為。創(chuàng)建一個(gè)ParentController,其中包含一個(gè)user對(duì)象,再創(chuàng)建一個(gè)ChildController來(lái)引用這個(gè)對(duì)象:
app.controller('ParentController', function($scope) {$scope.person = {greeted: false};});app.controller('ChildController', function($scope) {$scope.sayHello = function() {$scope.person.name = 'Ari Lerner';};});
如果我們將ChildController置于ParentController內(nèi)部,那ChildController的$scope對(duì)象的父級(jí)作用域就是ParentController的scope對(duì)象。根據(jù)原型繼承的機(jī)制,我們可以在子作用域中訪(fǎng)問(wèn)ParentController的scope對(duì)象。
例如,我們可以在ChildController的DOM元素中訪(fǎng)問(wèn)定義在ParentController中的person對(duì)象
<div ng-controller="ParentController"><div ng-controller="ChildController"><a ng-click="sayHello()">Say hello</a></div>{{ person }}</div>
我們看到,點(diǎn)擊按鈕時(shí),可以在ChildController中訪(fǎng)問(wèn)ParentController中$scope.person的值,就好像person對(duì)象定義在ChildController的scope中一樣。
控制器應(yīng)該盡可能保持短小精悍,而在控制器中進(jìn)行DOM操作和數(shù)據(jù)操作則是一個(gè)不好的實(shí)踐。
設(shè)計(jì)良好的應(yīng)用會(huì)將復(fù)雜的邏輯放到指令和服務(wù)中。通過(guò)使用指令和服務(wù),我們可以將控制器重構(gòu)成一個(gè)輕量且更易維護(hù)的形式
簡(jiǎn)潔的控制器:
angular.module('myApp', []).controller('MyController', function($scope,UserSrv) {// 內(nèi)容可以被指令控制$scope.onLogin = function(user) {UserSrv.runLogin(user);};});
前面已經(jīng)見(jiàn)過(guò)使用表達(dá)式的示例。用{{ }}符號(hào)將一個(gè)變量綁定到$scope上的寫(xiě)法本質(zhì)上就是一個(gè)表達(dá)式:{{ expression }}。當(dāng)用watch進(jìn)行監(jiān)聽(tīng)時(shí),AngularJS會(huì)對(duì)表達(dá)式或函數(shù)進(jìn)行運(yùn)算。
表達(dá)式和eval(javascript)非常相似,但是由于表達(dá)式由AngularJS來(lái)處理,它們有以下顯著不同的特性:
對(duì)表達(dá)式進(jìn)行的任何操作,都會(huì)在其所屬的作用域內(nèi)部執(zhí)行,因此可以在表達(dá)式內(nèi)部調(diào)用那些限制在此作用域內(nèi)的變量,并進(jìn)行循環(huán)、函數(shù)調(diào)用、將變量應(yīng)用到數(shù)學(xué)表達(dá)式中等操作。
盡管AngularJS會(huì)在運(yùn)行$digest循環(huán)的過(guò)程中自動(dòng)解析表達(dá)式,但有時(shí)手動(dòng)解析表達(dá)式也是非常有用的。
AngularJS通過(guò)$parse這個(gè)內(nèi)部服務(wù)來(lái)進(jìn)行表達(dá)式的運(yùn)算,這個(gè)服務(wù)能夠訪(fǎng)問(wèn)當(dāng)前所處的作用域。
將$parse服務(wù)注入到控制器中,然后調(diào)用它就可以實(shí)現(xiàn)手動(dòng)解析表達(dá)式。舉例來(lái)說(shuō),如果頁(yè)面上有一個(gè)輸入框綁定到了expr變量上,如下所示:
<div ng-controller="MyController"><input ng-model="expr" type="text" placeholder="Enter an expression" /><h2>{{ parseValue }}</h2></div>
我們可以在MyController中給expr這個(gè)表達(dá)式設(shè)置一個(gè)$watch并解析它:
angular.module("myApp", []).controller('MyController',function($scope,$parse) {$scope.$watch('expr', function(newVal, oldVal, scope) {if (newVal !== oldVal) {// 用該表達(dá)式設(shè)置parseFunvar parseFun = $parse(newVal);// 獲取經(jīng)過(guò)解析后表達(dá)式的值$scope.parsedValue = parseFun(scope);}});});
在AngularJS中,我們的確有手動(dòng)運(yùn)行模板編譯的能力。例如,插值允許基于作用域上的某個(gè)條件實(shí)時(shí)更新文本字符串。
要在字符串模板中做插值操作,需要在你的對(duì)象中注入$interpolate服務(wù)。在下面的例子中,我們將會(huì)將它注入到一個(gè)控制器中:
angular.module('myApp', []).controller('MyController',function($scope, $interpolate) {// 我們同時(shí)擁有訪(fǎng)問(wèn)$scope和$interpolate服務(wù)的權(quán)限});
$interpolate服務(wù)是一個(gè)可以接受三個(gè)參數(shù)的函數(shù),其中第一個(gè)參數(shù)是必需的。
$interpolate服務(wù)返回一個(gè)函數(shù),用來(lái)在特定的上下文中運(yùn)算表達(dá)式。
設(shè)置好這些參數(shù)后,就可以在控制器中進(jìn)行字符插值的操作了。例如,假設(shè)我們希望可以在電子郵件的正文中進(jìn)行實(shí)時(shí)編輯,當(dāng)文本發(fā)生變化時(shí)進(jìn)行字符插值操作并將結(jié)果展示出來(lái)。
<div ng-controller="MyController"><input ng-model="to"type="email"placeholder="Recipient" /><textarea ng-model="emailBody"></textarea><pre>{{ previewText }}</pre></div>
由于控制器內(nèi)部設(shè)置了一個(gè)需要每次變化都重新進(jìn)行字符插值的自定義輸入字段,因此需要設(shè)置一個(gè)$watch來(lái)監(jiān)聽(tīng)數(shù)據(jù)的變化。
簡(jiǎn)而言之,$watch函數(shù)會(huì)監(jiān)視scope上的某個(gè)屬性。只要屬性發(fā)生變化就會(huì)調(diào)用對(duì)應(yīng)的函數(shù)??梢允褂脀atch函數(shù)在scope上某個(gè)屬性發(fā)生變化時(shí)直接運(yùn)行一個(gè)自定義函數(shù)。
在控制器中,我們?cè)O(shè)置了$watch來(lái)監(jiān)視郵件正文的變化,并將emailBody屬性的值進(jìn)行字符插值后的結(jié)果賦值給previewText屬性。
angular.module('myApp', []).controller('MyController', function($scope, $interpolate) {// 設(shè)置監(jiān)聽(tīng)$scope.$watch('emailBody', function(body) {if (body) {var template = $interpolate(body);$scope.previewText =template({to: $scope.to});}};});在
現(xiàn)在,在{{ previewText }}內(nèi)部的文本中可以將{{ to }}當(dāng)做一個(gè)變量來(lái)使用,并對(duì)文本的變化進(jìn)行實(shí)時(shí)更新。
用startSymbol()方法可以修改標(biāo)識(shí)開(kāi)始的符號(hào)。這個(gè)方法接受一個(gè)參數(shù)。
用endSymbol()方法可以修改標(biāo)識(shí)結(jié)束的符號(hào)。這個(gè)方法也接受一個(gè)參數(shù)。
如果要修改這兩個(gè)符號(hào)的設(shè)置,需要在創(chuàng)建新模塊時(shí)將$interpolateProvider注入進(jìn)去。
angular.module('emailParser', []).config(['$interpolateProvider', function($interpolateProvider) {$interpolateProvider.startSymbol('__');$interpolateProvider.endSymbol('__');}]).factory('EmailParser', ['$interpolate', function($interpolate) {// 處理解析的服務(wù)return {parse: function(text, context) {var template = $interpolate(text);return template(context);}};}]);
現(xiàn)在,我們已經(jīng)創(chuàng)建了一個(gè)模塊,可以將它注入到應(yīng)用中,并在郵件正文的文本中運(yùn)行這個(gè)郵件解析器:
angular.module('myApp', ['emailParser']).controller('MyController', ['$scope', 'EmailParser',function($scope, EmailParser) {// 設(shè)置監(jiān)聽(tīng)$scope.$watch('emailBody', function(body) {if (body) {$scope.previewText = EmailParser.parse(body, {to: $scope.to});}});}]);
現(xiàn)在用自定義的 __ 符號(hào)取代默認(rèn)語(yǔ)法中的 {{ }} 符號(hào)來(lái)請(qǐng)求插值文本。
由于我們將表達(dá)式開(kāi)始和結(jié)束的符號(hào)都設(shè)置成了__,因此需要將HTML修改成用這個(gè)符號(hào)取代{{ }}的版本,
<div id="emailEditor"><input ng-model="to"type="email"placeholder="Recipient" /><textarea ng-model="emailBody"></textarea></div><div id="emailPreview"><pre>__ previewText __</pre></div>
聯(lián)系客服