Problems with circular dependency and OOP in AngularJS

ghz 1years ago ⋅ 6795 views

Question

AngularJS + OOP is kinda sexy feature to use

Hi, I'm successfully using OOP with AngularJs for some time already (first started with [angularjs with oop inheritance in action](https://stackoverflow.com/questions/17389291/angularjs-with-oop- inheritance-in-action)), the provided approach allows you define your classes as angular services, which you can later extend or inherit from like that:

Application.factory('AbstractObject', [function () {
    var AbstractObject = Class.extend({
        virtualMethod: function() {
           alert("Hello world");
        },
        abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
           throw new Error("Pure abstract call");
        }
    });

    return AbstractObject; // You return class definition instead of it's instance
}]);

Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
    var DerivedObject = AbstractObject.extend({
        virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
            alert("Hey!");

            this._super();
        },
        abstractMethod: function() {
            alert("Now I'm not abstract");
        }
    });

    return DerivedObject;
}]);

Plunker: http://plnkr.co/edit/rAtVGAsNYggBhNADMeoT

using the described approach gives you the ability to define classes that beautifully integrate into angular infrastructure. You get all sort of nifty features from two worlds - OOP and AngularJs. Dependency injection is free for your classes, and it makes your classes simple, allows putting a lot of boilerplate controller code into some base class that can be later reused.

However

AngularJs infrastructure blocks previously described approach from spreading it's wings on all 100%. The problem occurs when you try to define recursive class definitions (i.e. recursive aggregation), say you have two class definitions like Blog and Tag

Application.factory('Blog', ['Tag', function (Tag) {
    var Blog = Class.extend({
        tags: function() {
            return this.tags;
        }
    });

    return Blog;
}]);

Application.factory('Tag', ['Blog', function (Blog) {
    var Tag = Class.extend({
        Blogs: function() {
           return this.blogs;
        }
    });

    return Tag;
}]);

It won't work because both Blog and Tag are self-referencing themselves causing circular dependency.

P.S

The last thing, I have found kinda ugly solution that solves my problem in my specific case but doesn't work in general and as I said, it isn't pretty:

Application.factory('BlogNamespace', [function () {
    var Blog = Class.extend({
        tags: function() {
            return this.tags;
        }
    });

    var Tag = Class.extend({
        Blogs: function() {
           return this.blogs;
        }
    });

    return {
        Tag: Tag,
        Blog: Blog
    };
}]);

Question

The above fix won't work because namespaces may also be a subject of circular dependency. This means that it isn't solution to described problem but rather one level deeper problem now.

Any suggestions on how it is possible to solve described problem in general case?


Answer

A circular dependency is always the sign of mixing of concerns , which is a really bad thing. Miško Hevery, one of the authors of AngularJS, explains a nice solution [on his awesome blog](http://misko.hevery.com/2008/08/01/circular-dependency-in-constructors- and-dependency-injection/). In short, you probably have a third service hidden somewhere, which is the only part of your code really needed by the two others.