2013-09-23 55 views
22

我正在使用TodoMVC應用程序來更好地使用AngularJS框架。在index.html上線14-16看到這一點:

<form id="todo-form" ng-submit="addTodo()"> 
    <input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus> 
</form> 

通知的NG提交指令如何調用addTodo()未經newTodo模型作爲參數傳遞功能。

很短的時間後,我在同樣的文件,遇到下列代碼來到第19行:

<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)"> 

你可以看到筆者決定通過allChecked模式向markAll()這一次功能。如果我理解正確的話,他們可以參考$ scope.allChecked控制器內部,而不是通過它的。

爲什麼在同一個文件中使用兩種不同的方法?在某些情況下,某種方法更好嗎?這是一個不一致的情況還是有更深的邏輯被使用?

+1

這通常是一個範圍問題。如果使用爲每次迭代創建子作用域的ng-repeat,則需要將實例變量作爲參數傳遞。否則,你不知道它是什麼。除此之外,我會說這只是一個偏好和易於寫作的問題。 –

+0

我做了一些關於[這篇文章]範圍繼承的研究(http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs/14049482 #140494820)。爲了澄清我的具體問題,我正在尋找一些關於何時使用這種或那種方法的最佳實踐。如果它們相同,假設範圍相同,那麼我會接受該答案。 –

回答

30

我寧願永遠參數傳遞給函數:

  • 它更清晰什麼參數的函數需要。
  • 因爲所有參數注入功能它更容易單元測試(好單位 - 測試)

考慮以下情況:

$scope.addToDo = function(){ 
    //This declaration is not clear what parameters the function expects. 
    if ($scope.parameter1){ 
     //do something with parameter2 
    }  
} 

而且更要命:

$scope.addToDo = function(){ 
    //This declaration is not clear what parameters the function expects. 
    if ($scope.someobject.parameter1){ //worse 

    }  
} 

由於範圍繼承parameter2可能來自父範圍,在函數內部訪問parameter2創建一個緊耦合,當您嘗試單元測試該函數時也會造成麻煩。

如果我這樣定義函數:

//It's clearer that the function expects parameter1, parameter2 
$scope.addToDo = function(parameter1, parameter2){ 
    if (parameter1){ 
     //do something with parameter2 
    }  
} 

在你parameter2是由父母繼承範圍的情況下,你仍然可以從視圖中傳遞。當你進行單元測試時,很容易傳遞所有參數。

如果你曾經與ASP.NET MVC的工作,你會發現類似的東西:框架試圖注入參數轉化爲行動的功能,而不是直接從RequestHttpContext對象訪問它

這也是在其他情況良好有提到像處理ng-repeat

在我看來,控制器和模型在角度不明確分開。 $ scope對象看起來像我們的帶有屬性和方法的Model(Model還包含邏輯)。來自OOP背景的人會認爲:我們只傳入不屬於對象的參數。像一個班級人已經有hands,我們不需要爲每個對象方法傳遞hands。示例代碼:

//assume that parameter1 belongs to $scope, parameter2 is inherited from parent scope. 
    $scope.addToDo = function(parameter2){ 
     if ($scope.parameter1){ //parameter1 could be accessed directly as it belongs to object, parameter2 should be passed in as parameter. 
      //do something with parameter2 
     } 
    } 
1

也許可以說明你可以嗎?假設它們是相同的控制器,兩者之間沒有功能差異。請注意,有些情況下會生成子範圍,在這種情況下,您將不再具有與控制器相同的範圍。

2

我認爲這只是代碼中不一致的情況。我之前曾經思考過這個問題,並得出如下結論:

規則:不要將$ scope變量傳遞到$ scope函數中。

讀取控制器代碼應該有足夠的代碼來演示組件的功能。該視圖不應包含任何業務邏輯,僅包含綁定(ng-model,ng-click等)。如果視圖中的某些東西能夠通過移動到控制器而變得更加清晰,那就這樣吧。

例外:允許有條件納克級的語句(例如ng-class='{active:$index==item.idx') - 把類條件句的控制器可以是非常冗長,muddies控制器的邏輯與來自視圖的想法。如果它是一個可視屬性,請將其保存在視圖中。

例外:您正在與項目工作在NG-重複。例如:

<ul ng-repeat="item in items"> 
    <li><a ng-click="action(item)"><h1>{{item.heading}}</h1></a></li> 
</ul> 

我遵循這些規則寫控制器時&意見,他們似乎工作。希望這可以幫助。

+2

我同意保持控制器中的邏輯,而不是將$ scope變量傳遞到$ scope函數中,但是這不會減少單元測試代碼的能力,尤其是函數嗎?考慮一個函數displayMessage()與displayMessage(errMsg)相比較。第二個版本在我看來更容易測試。第一個版本依賴於實施細節和假設。你的答案似乎是一個好的開始,但我認爲它缺少關於在遵守這些其他規則的同時保持代碼可測試性的討論。 –

+0

@亞當托馬斯:是的,我用更多的信息更新了我的答案。 '如果你曾經使用ASP.NET MVC,你會注意到一些類似的東西:框架試圖將參數注入到action函數中,而不是直接從Request或HttpContext對象中訪問它。 –

+0

@AdamThomas我不相信這確實使代碼更少測試。 $ scope的內容定義了控制器的狀態,$ scope方法的行爲方式取決於控制器的狀態。另一點我會做的是,對於任何複雜的事情,你的方法可能需要考慮許多不同的變量來決定它的行爲。帶有大量參數的IMO函數不太可讀,在這種情況下,函數直接從$ scope讀取屬性會更好。 –

4

自定義行爲方法,如ng-click,ng-submit等允許我們傳遞參數給被調用的方法。這很重要,因爲我們可能想要傳遞一些可能無法自由傳遞的內容,直到控制器中的處理程序。對於例如,

angular.module('TestApp') 
.controller('TestAppController', ['$scope', function($scope) { 
    $scope.handler = function(idx) { 
     alert('clicked ' + idx.toString()); 
    }; 
}]); 

<ul> 
    <li ng-repeat="item in items"> 
     <button ng-click="handler($index)">{ item }</button> 
     <!-- $index is an iterator automatically available with ngRepeat --> 
    </li> 
</ul> 

在你的第二個例子的情況下,由於allChecked是形成markAll()同一控制的範圍之內,你是絕對正確的,也沒有必要通過任何東西。我們只創建另一個副本。

該方法將不得不簡單地重構使用範圍內可用的東西。

$scope.markAll = function() { 
    todos.forEach(function (todo) { 
     todo.completed = $scope.allChecked; 
    }); 
}; 

因此,即使我們在這些方法中使用的參數的選擇,他們只需要一些時間。

9

有兩個部分這個答案,第一部分回答哪一個是更好的選擇,另一部分是事實,他們既不是一個不錯的選擇!


哪一個是正確的?

這一個是:

$scope.addToDo = function(params1, ...) { 
    alert(params1); 
} 

爲什麼?因爲A-它是可測試的。即使不編寫測試,這一點也很重要,因爲可測試的代碼從長遠來看總是更具可讀性和可維護性。

由於B--它對呼叫者來說是不可知的。這個函數可以被任意數量的不同控制器/服務/等重用,因爲它不依賴於範圍的存在或者範圍的結構。

當你不是這樣做:

$scope.addToDo = function() { 
    alert($scope.params1); 
} 

A和B失敗。它本身不容易測試,並且不容易被重用,因爲您使用它的範圍可能會有不同的格式。

編輯:如果你正在做的事情非常緊密地聯繫在一起的具體範圍和運行從模板的功能,那麼你可能會在試圖使其可重複使用的只是沒有意義的運行情況。該功能根本不是通用的。在那種情況下,不要爲此煩惱,某些功能不能被重用。查看我寫作的默認模式,但請記住,在某些情況下,它不適合。


爲什麼都錯了?

因爲作爲一般規則,你不應該在你的控制器中做邏輯,那就是服務的工作。控制器可以使用服務並調用該函數或將其暴露在模型中,但不應該對其進行定義。

爲什麼這很重要?因爲它再次使得重用該功能變得容易。在控制器中定義的函數不能在其他控制器中重用,也不會限制在HTML中調用控制器的方式。在服務中定義的函數可以注入並在任何你喜歡的地方重用。

但我不需要重用該功能! - 是的,你做!也許不是現在,也許從來沒有這個特定的功能,但遲早你會最終想重用一個你相信你永遠不需要重用的函數。然後,你將不得不重寫你已經忘記了的代碼,這總是需要額外的時間。

最好從一開始就正確地做,並將所有可以運用的邏輯轉移到服務中。這樣,如果你曾經需要它們(甚至在另一個項目中),你可以抓住它並使用它,而不必重寫它以適應當前的作用域結構。

當然,服務不知道你的範圍,所以你不得不使用的第一個版本。獎金!而且不屬於整個範圍傳遞到服務的誘惑,永遠不會有好下場的:-)

所以這是IMO是最好的選擇:

app.service('ToDoService', [function(){ 
    this.addToDo = function(params1, ...){ 
     alert(params1); 
    } 
}]); 

,並在控制器內部:

$scope.addToDo = ToDoService.addToDo; 

請注意,我寫了 「一般規則」。在某些情況下,在控制器本身而不是服務中定義功能是合理的。例如,函數只涉及範圍特定的事情,比如以某種方式切換控制器中的狀態。在服務中沒有真正的方法可以做到這一點,而不會讓事情變得陌生。

但這聽起來似乎並非如此。

+0

非常贊同。現在,人們傾向於將業務邏輯放入模型中,該模型是具有特定於控制器的服務的模塊。在模塊內部,它可以調用其他服務提供的常用方法。 –

+1

我不同意。與控制器緊密耦合的視圖邏輯應位於控制器中。有時候,當顯示/啓用/禁用時,你有很多依賴關係的複雜接口。爲什麼只有在這個控制器中創建使用sesnse的服務? – Styx

+0

那麼我同意你:)正如我在最後一部分所說的,服務並不總是正確的答案,而不是當它直接與控制器相關時。在這種情況下,就像你說的那樣,爲什麼要提供服務時,它實際上不能被重用,因爲它取決於控制器和視圖。 –

9

Zen of Angular提示:

 
Treat scope as read only in templates 
Treat scope as write only in controllers 

按照這個原則,你應該總是電話功能明確地從模板參數。

但是,在您遵循的任何風格中,您必須注意priorities以及指令的執行順序。在你的例子中,using ng-model and ng-click leaves the order of execution of the two directives ambiguous。解決方法是使用ng-change,其中執行的順序是明確的:它將在之後的值僅發生變化