Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS common controller functionality - mix-in where needed or define on $rootScope?

I am using v1.2.0 rc2 of AngularJS and want to know what is the best method to provide common functionality to multiple controllers.

I have the following validation functions that I want to use in all controllers that edit a model:

$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
    };
};

I want to keep my controllers DRY so I defined a factory as follows:

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;
            }
        };
    }]);

Initially, I just mixed the factory into the controllers that needed it as follows:

$scope.canSave = validationFactory.isFormValid;

$scope.validationClasses = validationFactory.validationClasses;

But I realised I could add them to the $rootScope in the module's run method as follows:

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

Now they are available in every controller indiscriminately and there is less wiring up to do.

The functions are used in the view templates as follows:

<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>

My questions are:

  1. should I avoid adding common functions to the $rootScope? if so, what are the pitfalls?
  2. is it better to mix-in common functionality only where necessary?
  3. is there a better way of achieving the same outcome?

Updated Solution

I opted to use custom directives instead of adding functions to the $rootScope which had a nasty smell about it.

I created custom attributes validation-class-for="<input.name>" and disabled-when-invalid so the markup looks like this:

<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>

The directives simply require a form ancestor and watch a function to determine state.

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);
                });
            }
        };
    });

Now there is no need for the validation factory either.

like image 417
gwhn Avatar asked Oct 15 '13 12:10

gwhn


People also ask

What is rootScope and scope in AngularJS?

Root Scope All applications have a $rootScope which is the scope created on the HTML element that contains the ng-app directive. The rootScope is available in the entire application. If a variable has the same name in both the current scope and in the rootScope, the application uses the one in the current scope.

What is the responsibility of the controller in AngularJS?

The primary responsibility of an AngularJS Controller is to control the data that gets passed to the view. There is two-way communication between the scope and the view .

How we can add controllers in AngularJS explain it with an example?

AngularJS ExampleThe ng-controller="myCtrl" attribute is an AngularJS directive. It defines a controller. The myCtrl function is a JavaScript function. AngularJS will invoke the controller with a $scope object.


1 Answers

I would not suggest to use the scope for sharing logic. That reduces reusability and has influence when you want to test it.

  • I would suggest one of the following approaches: You can use class extension: http://ejohn.org/blog/simple-javascript-inheritance/ to extract common logic.
  • You can use the messaging of angular to broadcast events which toggle the state, like 'is valid' from a validation listener.
  • You can use an interceptor to prevent backend requests based on some conditions.
like image 135
Thomas Avatar answered Sep 21 '22 18:09

Thomas