AngularJS: когда передать переменную $scope в функцию
Я использую 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-submit вызывает функцию addTodo() без передачи модели newTodo в качестве аргумента.
Спустя короткое время я встретил следующий код в том же файле в строке 19:
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)">
Вы можете видеть, что автор решил передать модель allChecked функции markAll() на этот раз. Если я правильно понял, они могли бы ссылаться на $scope.allChecked внутри контроллера вместо того, чтобы передавать его.
Зачем использовать два разных подхода в одном файле? Является ли один подход лучше в некоторых случаях? Это случай несогласованности или используется более глубокая логика?
Ответы
Ответ 1
Я бы предпочел всегда передавать аргументы функции:
- Он уточняет, какие параметры ожидает функция.
- Это проще для модульного тестирования, поскольку все параметры вводятся в функцию. (полезно для тестирования модулей)
Рассмотрим следующую ситуацию:
$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 clearer that the function expects parameter1, parameter2
$scope.addToDo = function(parameter1, parameter2){
if (parameter1){
//do something with parameter2
}
}
Если ваш parameter2
наследуется от родительской области, вы все равно можете передать его из представления. Когда вы выполняете модульное тестирование, легко передать все параметры.
Если вы когда-либо работали с ASP.NET MVC, вы заметили нечто подобное: среда пытается вставлять параметры в функцию действия вместо обращения к ней непосредственно из Request
или HttpContext
object
Это также хорошо, если другие упоминали, как работать с ng-repeat
По-моему, контроллер и модель в angular не совсем четко разделены. Объект $scope выглядит как наша Модель со свойствами и методами (модель также содержит логику). Люди из фона ООП будут думать, что: мы передаем только параметры, которые не принадлежат объекту. Как и у класса Person уже есть 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
}
}
Ответ 2
В этом ответе есть две части: первая часть отвечает, какая из них лучше, другая часть - тот факт, что ни один из них не является хорошим вариантом!
Какой из них правильный?
Это:
$scope.addToDo = function(params1, ...) {
alert(params1);
}
Почему? Потому что A - это проверяемо. Это важно, даже если вы не пишете тесты, потому что проверяемый код в значительной степени всегда более читабельен и поддерживается в долгосрочной перспективе.
Это также лучше, потому что B - это агностик, когда дело доходит до вызывающего. Эта функция может быть повторно использована любым количеством различных контроллеров/сервисов/и т.д., Поскольку она не зависит от наличия области или структуры этой области.
Когда вы это сделаете:
$scope.addToDo = function() {
alert($scope.params1);
}
Оба A и B не работают. Это нелегко проверить самостоятельно, и его нельзя легко использовать повторно, потому что область, в которой вы его используете, может быть отформатирована по-разному.
Изменить:. Если вы делаете что-то очень тесно связанное с вашей конкретной областью и запускаете функцию из шаблона, то вы можете столкнуться с ситуациями, когда попытка сделать ее повторно используемой просто не делает смысл. Функция просто не является общей. В этом случае не беспокойтесь, некоторые функции нельзя использовать повторно. Просмотрите то, что я написал как ваш режим по умолчанию, но помните, что в некоторых случаях он не подходит.
Почему оба неправильно?
Потому что, как правило, вы не должны делать логику в своих контроллерах, это работа службы. Контроллер может использовать сервис и вызывать функцию или выставлять ее в модели, но не должен ее определять.
Почему это важно? Потому что снова это облегчает повторное использование функции. Функция, определенная в контроллере, не может быть повторно использована на другом контроллере, не устанавливая ограничений на способ вызова контроллеров в HTML. Функция, определенная в сервисе, может быть введена и использована повторно, где бы вы ни находились.
Но мне не нужно повторно использовать функцию! - Да, да! Может быть, не сейчас и, возможно, никогда не для этой конкретной функции, но рано или поздно вы в конечном итоге захотите повторно использовать функцию, в которой вы убеждены, что вам никогда не понадобится повторное использование. И тогда вам придется переработать код, который вы уже забыли, что всегда занимает дополнительное время.
Лучше просто сделать это правильно с самого начала и переместить всю логику, которую вы можете использовать в сервисах. Таким образом, если вам когда-нибудь понадобится их где-нибудь еще (даже в другом проекте), вы можете просто захватить его и использовать, не переписывая его, чтобы соответствовать вашей текущей структуре области.
Конечно, службы не знают о вашем объеме, поэтому вы вынуждены использовать первую версию. Бонус! И не поддавайтесь соблазну передать всю сферу обслуживания, которая никогда не закончится хорошо: -)
Итак, это ИМО лучший вариант:
app.service('ToDoService', [function(){
this.addToDo = function(params1, ...){
alert(params1);
}
}]);
И внутри контроллера:
$scope.addToDo = ToDoService.addToDo;
Обратите внимание, что я написал "общее правило". В некоторых случаях разумно определить функцию в самом контроллере, а не в сервисе. Одним из примеров может быть, когда функция относится только к конкретным областям, например, к переключению состояния в контроллере. Нет никакого реального способа сделать это в сервисе, если все станет странным.
Но похоже, что здесь не так.
Ответ 3
Zen Angular предлагает:
Treat scope as read only in templates
Treat scope as write only in controllers
Следуя этому принципу, вы всегда должны вызывать функции explicity с параметрами из шаблона.
Однако в любом стиле, который вы следуете, вам нужно быть осторожным в отношении priorities
и порядке выполнения директив. В вашем примере с использованием ng-model
и ng-click
оставляет порядок выполнения двух директив неоднозначным. Решение использует ng-change
, где порядок выполнения ясен: он будет выполнен только после изменения значения.
Ответ 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;
});
};
Следовательно, хотя у нас есть возможность использовать параметры в этих методах, они требуются только некоторое время.
Ответ 5
Я думаю, что это просто случай несогласованности кода. Я уже задумался над этим вопросом и пришел к следующему выводу...
Правило: Не передавать переменные $scope в функции $scope.
Чтение кода контроллера должно быть достаточным для демонстрации функции компонента. Представление не должно содержать никакой бизнес-логики, просто привязки (ng-model, ng-click и т.д.). если что-то в представлении может быть сделано более ясным, будучи перемещенным в контроллер, пусть будет так.
Исключение: Разрешить условные инструкции ng-класса (например, ng-class='{active:$index==item.idx'
) - положить условные обозначения классов в контроллер может быть очень многословным и путать логику контроллера с идеями из представления. Если это визуальное свойство, сохраните его в представлении.
Исключение:. Вы работаете с элементом в ng-repeat. Например:
<ul ng-repeat="item in items">
<li><a ng-click="action(item)"><h1>{{item.heading}}</h1></a></li>
</ul>
Я следую этим правилам при написании контроллеров и представлений, и они, похоже, работают. Надеюсь, это поможет.
Ответ 6
Возможно, чтобы проиллюстрировать это, вы можете? Между ними нет функциональной разницы, предполагая, что они являются одним и тем же контроллером. Обратите внимание, что есть ситуации, когда генерируются дочерние области, и в этом случае вы больше не будете иметь ту же область действия, что и контроллер.