Question

This question is about half practical and half conceptual. I've looked at the responses to similar questions, but I'm pretty new to AngularJS, and I'm just not sure the best way (I've seen some varying opinions) to do this (for me, anyway), or really, the actual code I would write to do it, which is why I'm asking the question with the specifics of my own application.

I know there are lots of questions here with similar titles, but I urge you to keep reading.

In short, I have a bunch of controllers, because I have a lot of models I'm pulling together into a single page. When the front end dispatches a request (i.e., user action) to any of my back end controllers, the front end is going to get a response that may look something like this:

{"success":false,"errors":[{"text":"This is an error sent from the PHP controller.","type":"critical"}]}

I want to use AngularJS, however, to create the model and view for my error log (it only has to live on the client side). So in other words, every other controller in the application is going to need to have access to the error log controller in order to add events to the error log.

I guess I'm aware of some of the options, like creating a shared service/factory and broadcasting to the rootscope. I'm also wondering if it makes sense at all to make every other controller a child of the controller that handles errors, alerts, etc., though instinctively, that feels wrong to me.

What's the best way to do it (keeping in mind that the same controller that handles errors might also handle things like alerts and other global-type housekeeping), and would somebody be kind enough to help me out with the actual code based upon this model I mocked up for what the behavior would look like?

Here it is at JSFiddle: http://jsfiddle.net/Ww8sS/2/

And here's the code. There are probably a number of things in here that wouldn't be the best way to do something, but for now, I'm just concerned with the problem I've described.

JS:

var userInterfaceApp = angular.module('user-interface', ['userInterfaceFilters']);

userInterfaceApp.controller('AnotherController', ['$scope', '$http', function($scope, $http) {

    $scope.doSomething = function() {
        $http({method: "JSONP", url: "http://uatu.net/test.php?action=do_something&callback=JSON_CALLBACK"}).
        success(function(data) {
            if(!data.success) {
                alert("How do I get the errors in data.errors to my error log?");
            }
        })
        .error(function(data, status, headers, config) {
            alert("Failure");
        // called asynchronously if an error occurs
        // or server returns response with an error status.
        });
    }
}]);

userInterfaceApp.controller('ConsoleEventController', ['$scope', function($scope) {
    $scope.errorLog = [];
    $scope.errorSortOrder = "-timestamp";

    $scope.addToErrorLog = function(errorArray) {
        for (var i = 0; i < errorArray.length; i++) {
            $scope.errorLog.push({"text" : errorArray[i].text, "type" : errorArray[i].type, "timestamp" : new Date()});
        }
    }

    //Not a real method--just here for demonstration
    $scope.createErrorMessage = function() {
        $scope.addToErrorLog([{"text" : "This is a sample error.", "type" : "critical"}]);
    }
}]);

var userInterfaceFilters = angular.module("userInterfaceFilters", []);

userInterfaceFilters.filter("logTimestamp", function() {
    return function(logDate) {
        var hours = logDate.getHours();
        var minutes = (logDate.getMinutes() < 10) ? "0" + logDate.getMinutes() : logDate.getMinutes();
        var seconds = (logDate.getSeconds() < 10) ? "0" + logDate.getSeconds() : logDate.getSeconds();
        return hours + ':' + minutes + ":" + seconds;
    };
});

I had to use JSONP to make it work on JSFiddle. I wouldn't do that in my actual program, since it will all be on my server.

HTML:

<div ng-app="user-interface">
    <div ng-controller="AnotherController">
    <input type="button" value="Do Something" ng-click="doSomething()">    
    </div>

    <div ng-controller="ConsoleEventController">
        <p><input type="button" value="Create Error Message" ng-click="createErrorMessage()"></p>
        <h1>Error Log</h1>
        <ul id="error-log">
            <li ng-repeat="error in errorLog | orderBy:errorSortOrder" class="error-{{error.type}}">&lt;{{error.timestamp|logTimestamp}}&gt; {{error.text}}</li>
        </ul>
    </div>
</div>
Was it helpful?

Solution

Sounds like you already know the best approach is to go down the factory/service route. There's no need to broadcast though - they just create a single instance which you can inject wherever necessary.

Here's a quick example of how you can go about it: http://jsfiddle.net/Ww8sS/3/

OTHER TIPS

For me make more sense using a messaging paradigm (broadcast) rather than use factory that create a variable that after you bind to scope of the controller, because it does that the controller and the service/factory are coupled so you could never change the variable of the service/factory due that your controller would lose the link with the service/factory variable.

For example, if you would like to create a new method in the service/factory that clear the log's array so you create new array rather than empty the current array then that change wouldn't be reflected in that controller's scope because the scope's variable point to the old one log's array; take a look the example: http://jsfiddle.net/Ww8sS/6/

var userInterfaceApp = angular.module('user-interface', ['userInterfaceFilters']);

userInterfaceApp.factory('errorLogs', function () {
    return {
        logs: [],
        addToErrorLog: function (errorArray) {
            for (var i = 0; i < errorArray.length; i++) {
                this.logs.push({"text": errorArray[i].text, "type": errorArray[i].type, "timestamp": new Date()});
            }
        },
        clearLogs: function () {
            this.logs = [];
        }
    }
});

userInterfaceApp.controller('AnotherController',
    ['$scope', '$http', 'errorLogs', function ($scope, $http, errorLogs) {

        $scope.doSomething = function () {
            $http({method: "JSONP", url: "http://uatu.net/test.php?action=do_something&callback=JSON_CALLBACK"}).
                success(function (data) {
                    if (!data.success) {
                        errorLogs.addToErrorLog(data.errors);
                    }
                })
                .error(function (data, status, headers, config) {
                    alert("Failure");
                    // called asynchronously if an error occurs
                    // or server returns response with an error status.
                });
        }
    }]);

userInterfaceApp.controller('ConsoleEventController',
    ['$scope', 'errorLogs', function ($scope, errorLogs) {
        $scope.errorLog = errorLogs.logs;
        $scope.errorSortOrder = "-timestamp";

        //Not a real method--just here for demonstration
        $scope.createErrorMessage = function () {
            errorLogs.addToErrorLog([
                {"text": "This is a sample error.", "type": "critical"}
            ]);
        }

        $scope.clearLogs = function () {
            errorLogs.clearLogs();
        };

    }]);

var userInterfaceFilters = angular.module("userInterfaceFilters", []);

userInterfaceFilters.filter("logTimestamp", function () {
    return function (logDate) {
        var hours = logDate.getHours();
        var minutes = (logDate.getMinutes() < 10) ? "0" + logDate.getMinutes() : logDate.getMinutes();
        var seconds = (logDate.getSeconds() < 10) ? "0" + logDate.getSeconds() : logDate.getSeconds();
        return hours + ':' + minutes + ":" + seconds;
    };
});

If you use the messaging paradigm, it uncouple controllers with the service, moreover that the service is independent and any controller can listen its events; http://jsfiddle.net/Ww8sS/5/

    var userInterfaceApp = angular.module('user-interface', ['userInterfaceServices', 'userInterfaceFilters']);

    userInterfaceApp.controller('AnotherController', ['$scope', '$http', 'logger', function($scope, $http, logger) {

        $scope.doSomething = function() {
            $http({method: "JSONP", url: "http://uatu.net/test.php?action=do_something&callback=JSON_CALLBACK"}).
            success(function(data) {
                if(!data.success) {
                    logger.addToErrorLog(data.errors);
                    //alert("How do I get the errors in data.errors to my error log?");
                }
            })
            .error(function(data, status, headers, config) {
                alert("Failure");
            // called asynchronously if an error occurs
            // or server returns response with an error status.
            });

        }

         $scope.clearLog = function() {
          logger.clearLog();      
        }
    }]);

    userInterfaceApp.controller('ConsoleEventController', ['$scope', function($scope) {
        $scope.errorSortOrder = "-timestamp";
        $scope.errorLog;

        $scope.$on('logger.newErrors', function (evt, errArray) {  
             $scope.errorLog = errArray;
            });

        $scope.$on('logger.clearLog', function (evt) {  
             $scope.errorLog = [];
            });


        //Not a real method--just here for demonstration
        $scope.createErrorMessage = function() {
           // $scope.addToErrorLog([{"text" : "This is a sample error.", "type" : "critical"}]);
        }
    }]);

var userInterfaceFilters = angular.module("userInterfaceFilters", []);

userInterfaceFilters.filter("logTimestamp", function() {
    return function(logDate) {
        var hours = logDate.getHours();
        var minutes = (logDate.getMinutes() < 10) ? "0" + logDate.getMinutes() : logDate.getMinutes();
        var seconds = (logDate.getSeconds() < 10) ? "0" + logDate.getSeconds() : logDate.getSeconds();
        return hours + ':' + minutes + ":" + seconds;
    };
});


var userInterfaceServices = angular.module('userInterfaceServices', []);

userInterfaceServices.service('logger', ['$rootScope', function ($rootScope) {

        var errorLog = [];

        this.addToErrorLog = function(errorArray) {
            for (var i = 0; i < errorArray.length; i++) {
                errorLog.push({"text" : errorArray[i].text, "type" : errorArray[i].type, "timestamp" : new Date()});
            }

            $rootScope.$broadcast('logger.newErrors', errorLog);
        };

    this.clearLog = function () {
      errorLog = [];
        $rootScope.$broadcast('logger.clearLog', '');
    };
}]);

Anyway, both solutions have some advantages and drawbacks.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top