15

I'm making a logger service, to extend angular's $log service, by saving the errors (or debug if enabled) into an indexedDB database. Here's the code:

angular.module('appLogger', ['appDatabase'])

.service('LogServices', function($log, Database) {

    // ...

    this.log = function(type, message, details) {
        var log = {};
        log.type = type
        log.context = this.context;
        log.message = message;
        log.dateTime = moment().format('YYYY-MM-DD HH:mm:ss');
        log.details = details || '';
        $log[type.toLowerCase()](log);

        if (type === 'ERROR' || this.logDebug) {
            Database.logSave(log);
        }
    };

    // ...
})

This is working in my services as intended. Now the problem is I can't use my logger inside the Database service, because it throws a circular dependency error. I understand the problem but I have no clue how should I resolve it... How should I get around this ?

Thanks for helping :-)

ImSoNuts
  • 167
  • 1
  • 1
  • 5
  • 1
    Please have a look at my answer to a similar question: http://stackoverflow.com/questions/20647483/angularjs-injecting-service-into-a-http-interceptor-circular-dependency/21632161#21632161 – Pieter Herroelen Apr 07 '14 at 09:52

3 Answers3

34

The reason Angular is complaining about a circular dependency is that...well there is one.
It is a very dangerous path to go down, but if you know what you are doing (famous last words) then there is a solution to circumvent that:

.service('LogServices', function($log, $injector) {

    // ...

    var Database;   // Will initialize it later

    this.log = function(type, message, details) {
        /* Before using Database for the first time
         * we need to inject it */
        if (!Database) { Database = $injector.get('Database'); }

        var log = {};
        log.type = type
        log.context = this.context;
        log.message = message;
        log.dateTime = moment().format('YYYY-MM-DD HH:mm:ss');
        log.details = details || '';
        $log[type.toLowerCase()](log);

        if (type === 'ERROR' || this.logDebug) {
            Database.logSave(log);
        }
    };

    // ...
})

See, also, this short demo.

gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • Thank you, it is working this way. Now what do you think would be the safe path ? Why is this path dangerous ? Can you elaborate please ? I would very much like to do things the angular way, and if possible not to use hacks. Question is, do I have another option ? – ImSoNuts Apr 07 '14 at 10:02
  • 3
    The dangerous thing is to get into an infinite loop that will crush your app (that is what Angular is trying to protect you from in the first place). If you want to see that in action, go to my demo and uncomment the line `service1.serve();` The "Angular" way is not to create circular dependencies (and in this case I think yu can't, because you need `Database` to provide `LogServices` and you want to also have `LogServices` available in `Database` (which is a paradox in itself :)). – gkalpak Apr 07 '14 at 10:10
  • 1
    If you want to "play it safe", I see 2 options: 1.) For `Database` use a simpler logging mechanism (that does not rely on `Database`). 2.) Don't use the same `Database` service for `LogServices` and for the rest of the app. Use a simpler `LogDatabase` service (that doesn'trely on `LogServices`) to pass to `LogServices` and use your current `Database` service for the rest of your app (which can safely use `LogServices` for logging). (I hope this makes sense :P) – gkalpak Apr 07 '14 at 10:14
  • I figured I would have to manually save the errors and not rely on the Database service. I can't do the other way around cause Database service is a big one and therefore relies heavily on logging. Damn I thought there would be an alternative. Thank you anyway for the heads up :) – ImSoNuts Apr 07 '14 at 12:12
3

See this answer and in particular Misko's blog and in particular Peter's question in the comments, in which almost the exact same problem is discussed.

Misko's answer is (using Java code)

class Database() implements DB;
class Logger(Database db);
class LoggingDatabase(Logger log, Database db) implements DB;

So in your application, you have

.service('Database', ...) // DO NOT inject the $log or LogServices here

.service('LogServices', function($log) {...}) // DO NOT inject Database here

.service('LoggingDB', function(Database, LogServices) {...}) 

Use LoggingDB for every part of your app where you want a database that logs. (Or is it a logger that uses the database!)

Another thought

How is it that you have a Database on the browser? Is Database a wrapper for $http or $resource or something? If that's the case, I agree with ExpertSystem in his 2) option: don't use $http for logging errors because what if the error kills $http? Use XMLHTTPRequest instead. See here for some discussion.

Community
  • 1
  • 1
poshest
  • 4,157
  • 2
  • 26
  • 37
0

I faced this issue when trying to override exceptionHandler

here is the code where the issue appeared

angular
    .factory('$exceptionHandler', ExceptionHandler);

function exceptionHandler($log, sweetAlert) {// here is the problem with injecting sweetAlert
    return function myExceptionHandler(exception, cause) {
        //logErrorsToBackend(exception, cause);
        sweetAlert.swal('Opps...', 'An error has occurred');
        $log.warn(exception, cause);
    };
}

and to fix it I used injector

angular
    .factory('$exceptionHandler', ExceptionHandler);

ExceptionHandler.$inject = ['$injector']; //better for minification

function ExceptionHandler($injector) {
    var $log, sweetAlert, $translate;// use variables for caching

    return function exceptionHandler(exception, cause) {
        // Add DI here to prevent circular dependency
        $log = $log || $injector.get('$log');
        sweetAlert = sweetAlert || $injector.get('sweetAlert');
        $translate = $translate || $injector.get('$translate');
        //............
    }

here is a link for more info Injecting $http results in a Circular dependency

hope this helps you

Basheer AL-MOMANI
  • 14,473
  • 9
  • 96
  • 92