Search code examples
javascriptangularjscontrollersbroadcasting

my error log - sharing its methods across controllers in AngularJS


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>

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/