4

I am having a quite complex dependency structure within the data model of my Angular app.

In an abstract form, it looks like this (A -> B means "A imports B"):

enter image description here

From this structure I get many circular dependencies and it is difficult for me to solve the problem. I already tried to apply the solution mentioned here: I tried using an internal.ts file and exporting all of the classes within that file. When importing any of the classes, I imported them from the ./internal.ts file again.

Still, the problem persists. The only thing that changes is the form of the error messages:

From

Circular dependency detected: C.ts -> D.ts -> E.ts -> C.ts

to

Circular dependency detected: C.ts -> internal.ts -> C.ts
Circular dependency detected: D.ts -> internal.ts -> D.ts
Circular dependency detected: E.ts -> internal.ts -> E.ts

Is there any other way to solve a problem like this?

1 Answer 1

8

The solution proposed in the linked article does not eliminate circular dependencies – it merely avoids any disastrous effects they may cause by carefully controlling the load order. To quote:

The reason that this solves our problem is: we now have full control over the module loading order. Whatever the import order in internal.js is, will be our module loading order.

Circular dependencies are only problematic and therefore the module load order is only really important if you have modules that evaluate their imports immediately as they are loaded – i.e. the imported values are used in the main body of the module:

// Foo.js
import { Bar } from "./Bar"

export class Foo {
  getBarClass() {
    return Bar;
  }
}


// Bar.js
import { Foo } from "./Foo"

console.log(Foo.name);           // Foo eval'ed on load :(
export const fooClass = Foo;     // Foo eval'ed on load :(

export class Bar extends Foo {   // Foo eval'ed on load :(
  static fooClass = Foo;         // Foo eval'ed on load :(
  fooClass = Foo;                // Eval'ed only on new Bar()

  static getFooClass() {        
    return Foo;                  // Eval'ed only on Bar.getFooClass()
  }

  getFooClass() {                
    return Foo;                  // Eval'ed only on someBar.getFooClass()
  }
}

In this example Foo.js does not access Bar at load time, only later when getBarClass is called. On the other hand, Bar.js does access the imported Foo multiple times at load time. Therefore, Bar.js has to be loaded first.

Circular dependencies cease to be an issue once you no longer access imports immediately at load time:

// execute in next tick
setImmediate(() => console.log(Foo.name));

// wrap in function
export const getFooClass = () => Foo;

// wrap in function
export const getBarClass = () => {
  return class Bar extends Foo {
    static fooClass = Foo;
    fooClass = Foo;
  };
};

Of course these solutions for delayed evaluation look rather ugly so in the end you may want to go with the carefully coordinated load order after all.

Sign up to request clarification or add additional context in comments.

2 Comments

I simply do not like warnings in my build. If I toy with the load order, can I make these warnings go away? Or, is it always possible to have one that can't be solved?
@DanielKaplan That depends on how your code is structured. I have yet to work on a non-trivial Angular or Node.js project that didn't have them. I usually just turn off the warnings...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.