Функционалност на общия контролер на AngularJS - смесване, където е необходимо, или дефиниране на $rootScope?

Използвам v1.2.0 rc2 на AngularJS и искам да знам кой е най-добрият метод за предоставяне на обща функционалност на множество контролери.

Имам следните функции за валидиране, които искам да използвам във всички контролери, които редактират модел:

$scope.canSave = function (formController) {
    return formController.$dirty && formController.$valid;
};

$scope.validationClasses = function (modelController) {
    return {
        'has-error': modelController.$invalid && modelController.$dirty,
        'has-success': modelController.$valid && modelController.$dirty
    };
};

Искам да запазя моите контролери СУХИ, затова дефинирах фабрика, както следва:

angular.module('myModule', [])
    .factory('validationFactory', [function () {
        return {
            validationClasses: function (modelController) {
                return {
                    'has-error': modelController.$invalid && modelController.$dirty,
                    'has-success': modelController.$valid && modelController.$dirty
                };
            },
            isFormValid: function (formController) {
                return formController.$dirty && formController.$valid;
            }
        };
    }]);

Първоначално просто смесих фабриката в контролерите, които се нуждаеха от нея, както следва:

$scope.canSave = validationFactory.isFormValid;

$scope.validationClasses = validationFactory.validationClasses;

Но разбрах, че мога да ги добавя към $rootScope в метода за изпълнение на модула, както следва:

angular.module('myModule', [])
    .run([
        '$rootScope',
        'validationFactory',
        function ($rootScope, validationFactory) {
            $rootScope.canSave = $rootScope.canUpdate = validationFactory.isFormValid;
            $rootScope.validationClasses = validationFactory.validationClasses;
        }]);

Сега те са налични във всеки контролер без разбор и има по-малко необходимост от окабеляване.

Функциите се използват в шаблоните за преглед, както следва:

<form name="questionForm" novalidate>
    <div class="form-group" ng-class="validationClasses(questionForm.question)">
        <label for="questionText" class="control-label">Text</label>
        <input type="text" ng-model="question.text" name="question"
               id="questionText" class="form-control" required/>
        <span ng-show="questionForm.question.$error.required"
              class="help-block">Question text is required</span>
    </div>
    ...
    <div class="form-group" ng-switch on="action">
        <button type="button" ng-switch-when="New" ng-click="save()" 
                ng-disabled="!canSave(questionForm)" 
                class="btn btn-primary">Save</button>
        <button type="button" ng-switch-default ng-click="update()" 
                ng-disabled="!canUpdate(questionForm)" 
                class="btn btn-primary">Update</button>
        <button type="button" ng-click="cancel()"
                class="btn btn-default">Cancel</button>
    </div>
</form>

Въпросите ми са:

  1. трябва ли да избягвам добавянето на общи функции към $rootScope? ако е така, какви са капаните?
  2. по-добре ли е да се смесват общи функции само когато е необходимо?
  3. има ли по-добър начин за постигане на същия резултат?

Актуализирано решение

Избрах да използвам персонализирани директиви, вместо да добавя функции към $rootScope, което имаше неприятна миризма.

Създадох персонализирани атрибути validation-class-for="<input.name>" и disabled-when-invalid, така че маркирането изглежда така:

<div class="form-group" validation-class-for="question">
    <label for="questionText" class="control-label">Text</label>
    <input type="text" ng-model="question.text" name="question"
           id="questionText" class="form-control" required/>
    <span ng-show="questionForm.question.$error.required"
          class="help-block">Question text is required</span>
</div>

<button type="button" ng-click="save()" disabled-when-invalid 
    class="btn btn-primary">Save</button>

Директивите просто изискват предшественик на форма и наблюдават функция, за да определят състоянието.

angular.module('myModule', [])
    .directive('validationClassFor', function () {
        return {
            require: '^form',
            link: function (scope, element, attributes, formController) {
                scope.$watch(function () {
                    var field = formController[attributes.validationClassFor];
                    if (field.$invalid && field.$dirty) {
                        element.removeClass('has-success').addClass('has-error');
                    } else if (field.$valid && field.$dirty) {
                        element.removeClass('has-error').addClass('has-success');
                    } else {
                        element.removeClass('has-error').removeClass('has-success');
                    }
                });
            }
        };
    })
    .directive('disabledWhenInvalid', function () {
        return {
            require: '^form',
            link: function (scope, element, attributes, formController) {
                scope.$watch(function () {
                    return formController.$dirty && formController.$valid;
                }, function (value) {
                    element.prop('disabled', !value);
                });
            }
        };
    });

Сега няма нужда и от фабриката за валидиране.


person gwhn    schedule 15.10.2013    source източник


Отговори (2)


Как използвате тази validationFactory? Трябва ли да промените външния вид на бутоните за изпращане? Ако е така, предлагам да създадете персонализирани компоненти за самите бутони и компонентът да препраща към validationFactory.

person Sam Barnum    schedule 15.10.2013
comment
Използвам функциите, за да контролирам дали бутонът Запазване/Актуализиране е деактивиран и CSS класовете, които се прилагат към групата формуляри. - person gwhn; 15.10.2013

Не бих предложил да се използва обхватът за споделяне на логика. Това намалява повторното използване и има влияние, когато искате да го тествате.

  • Бих предложил един от следните подходи: Можете да използвате разширение на клас: http://ejohn.org/blog/simple-javascript-inheritance/ за извличане на обща логика.
  • Можете да използвате съобщенията на angular за излъчване на събития, които превключват състоянието, като „е валидно“ от слушател за валидиране.
  • Можете да използвате прехващач, за да предотвратите задните заявки въз основа на някои условия.
person Thomas    schedule 15.10.2013
comment
Мисля, че правите добра гледна точка относно въздействието върху възможността за тестване. TBH Зададох въпроса, защото мислех, че е кодова миризма. - person gwhn; 15.10.2013