1

I'm using TypeScript + AMD (RequireJs) and I'm facing a problem regarding circular dependency. tsc deals with circular dependency pretty smooth but when it's run time, it took me hours to figure out that I'm facing a circular dependency problem.

Here's my scenario:

Singleton.ts

import Window = require("./Window");

class Singleton
{
    private w : Window;
    private counter : number = 0;

    constructor() {
        w = new Window();
    }

    Increment() : number {
        return ++this.counter;
    }
}
export = Singleton;

Window.ts

import Singleton = require("./Singleton");
declare var global_instance : Singleton;

class Window
{
    private my_number : number = 0;

    constructor() {
        this.my_number = global_instance.Increment();
    }
}
export = Window;

As simple as it seems, it leads to a circular dependency and thus I'm unable to implement it in a browser. But as I said tsc deals with it perfectly without any problem. As I searched the Internet I found a couple of proposed solutions:

This one suggests adding a typeof before the type to prevent compiler from actually putting the type's file in require list. But it results in a dozen of compiler errors so it's out.

This one sounds promising but it's a RequireJs solution and I'm not sure how to implement it in TypeScript!

Does anyone have any solution how to deal with circular dependency while benefiting from type checking of tsc compiler?

Community
  • 1
  • 1
Mehran
  • 15,593
  • 27
  • 122
  • 221
  • This sounds stupid, but do you really need to introduce circular dependency? Shouldn't the design/code be written so that you don't have it in the first place? – AD.Net Jul 30 '14 at 20:12
  • It would be perfect if you could point me to the right direction. How would you design the mentioned scenario? Unless you want to duplicate `Singleton`'s `class` into an `interface` which is redundant and my last resort. I'm hoping to find something more practical. – Mehran Jul 30 '14 at 20:18
  • How'd you do it in something like C# where you can't have circular dependency? Where does the instance count really belong? Why does the `Window` have to increment some other class's count? – AD.Net Jul 30 '14 at 20:22
  • 2
    I feel your pain. Because circular dependencies are hard to tackle in (modular) Javascript, and an even greater pain in Typescript, some people have invented a new "Pattern": if you have them, your design is wrong. Don't listen, 99% of Java code would not run if this were true. There are a few workaround in Javascript, less or none in Typescript. I created this [issue](http://typescript.codeplex.com/workitem/2613) some time ago, but it did not trigger much interest. I had to ditch typescript for a project due to this. Good luck. – Bruno Grieder Jul 30 '14 at 20:59

2 Answers2

2

Because these files are absolutely tied together, put them in a single file. Not only does it solve your circular reference, but it also means one less HTTP request (which you are guaranteed to need).

Once they are in the same file, you may decide to merge the concepts further, or split out that part that they both depend on to remove the circular dependency.

Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 2
    +1. Never a good idea to leave a circular dependency between files – basarat Jul 31 '14 at 00:06
  • 1
    Atom TypeScript now does circular dependency analysis : https://github.com/TypeStrong/atom-typescript/blob/master/docs/dependency-view.md#circular – basarat Apr 06 '15 at 03:12
  • 2
    @Sohnee Having two files that reference each other is a very common thing in software architectures and especially javascript. Following your advice of combining everything into a single file would be a disaster. 80% of the code of a large app would end up in the same file. Sure i'm going to combine into single file for distribution, but modules need to be able to be separate files, and discreet. The fact that TypeScript cannot handle circular references, is definitely a serious problem. The solution is most certainly not putting everything in one file. –  Jul 06 '16 at 00:37
0

if you are using the requirejs.d.ts

amend it with this

declare var exports: any;

and then your class you explicitly call exports.

import Window = require("./Window");

class Singleton
{
    private w : Window.Window;
    private counter : number = 0;

    constructor() {
        w = new Window.Window();
    }

    Increment() : number {
        return ++this.counter;
    }
}
exports.Singleton = Singleton;
export = Singleton;

Window.ts

import Singleton = require("./Singleton");
declare var global_instance : Singleton.Singleton;

class Window
{
    private my_number : number = 0;

    constructor() {
        this.my_number = global_instance.Increment();
    }
}
exports.Window = Window;
export = Window;

I wish there was a way, such as in node to just set exports directly but that does not seem to work, at least for me.

Adam
  • 131
  • 1
  • 2