Подтвердить что ты не робот

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 внутри контроллера вместо того, чтобы передавать его.

Зачем использовать два разных подхода в одном файле? Является ли один подход лучше в некоторых случаях? Это случай несогласованности или используется более глубокая логика?

4b9b3361

Ответ 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

Возможно, чтобы проиллюстрировать это, вы можете? Между ними нет функциональной разницы, предполагая, что они являются одним и тем же контроллером. Обратите внимание, что есть ситуации, когда генерируются дочерние области, и в этом случае вы больше не будете иметь ту же область действия, что и контроллер.