2

I just stumbled upon the circular dependency problem when building a project I am working on: a small ORM, for learning purposes.

Minimal project to reproduce the problem can be found here. Here is an even simpler overview:

Article:

import { ManyToMany } from './ManyToMany';
import { Tag } from './Tag';

export class Article {
    @ManyToMany(Tag)
    tags: Tag[] = [];
}

ManyToMany:

//no imports
export function ManyToMany(entity) {
    …
}

Tag:

import { ManyToMany } from './ManyToMany';
import { Article } from './Article';

export class Tag {
    @ManyToMany(Article)
    articles: Article[] = [];
}

It is important that the code is not changed too much or at all because this affects the DX. I don't want the users of this library to create additional hacky files to fix this problem.

I found a list of discussions but none of them are elegant:

The best solution that I could find is using the internal module pattern. This and all other solutions are just hacks and workarounds to this problem and none actually fixes it.

Is there a better, more elegant solution to this without creating additional files or move code around?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Ionel Lupu
  • 2,695
  • 6
  • 29
  • 53
  • 1
    How do you use those imports? – Jonas Wilms Oct 20 '19 at 19:18
  • The problem is that your `ManyToMany` decorator gets passed a value, the class object itself. You'd need it to accept a type, but I have no idea how that might be possible. – Bergi Oct 20 '19 at 19:21
  • Or maybe you shouldn't have it accept an argument at all, but rather make it introspect the type declaration of the property that it was invoked on? I have no idea whether that is possible in TypeScript unfortunately, but it would be the ideal solution. – Bergi Oct 20 '19 at 19:24
  • 2
    A solution that would certainly work is to make the reference lazy, i.e. pass a function returning the entity and do not evaluate it immediately (during the property decoration) but later when all the modules are loaded, the classes initialised, and the first instance is being created: `@ManyToMany(() => Tag)` – Bergi Oct 20 '19 at 19:26
  • I tried the lazy loading but it doesn't work https://stackoverflow.com/questions/63953101/typescript-circular-dependency-on-decorator – gio Sep 19 '20 at 08:53

1 Answers1

0

As per Bergi's comment, the solution that would work is to make the reference lazy i.e. pass a function returning the entity and do not evaluate it immediately (during the property decoration) but later when all the modules are loaded, the classes initialized, and the first instance is being created.

I have tried to implement the same https://stackblitz.com/edit/typescript-f5rm9u here. Please check the implementation.

ManyToMany

export function ManyToMany(entity=() => entity) {
    return function(target, name)  {
        console.log(entity.name);
    };
}

Answer credit: @Bergi

Prashant Biradar
  • 301
  • 5
  • 14
  • 1
    No, `Tag => Tag` is as meaningless as `x => x`. Your implementation doesn't log any names at all. – Bergi Oct 20 '19 at 20:32
  • I've used this answer and your answer @Bergi and also added a setTimeout() inside ManyToMany and it works. The only problem with this is that the code is async now. – Ionel Lupu Oct 20 '19 at 20:44
  • No need to make it async, it's enough to defer it until the instance is created (or whatever thing that your decorator is supposed to create will be executed) – Bergi Oct 20 '19 at 21:06
  • @Bergi please check, https://stackblitz.com/edit/typescript-f5rm9u here. you can see log for article entity with 0 tags. Also, we can create an instance of the tag, so here we are using arrow x=>x or tag => tag means it is referring to the current invoking instance. – Prashant Biradar Oct 20 '19 at 21:16
  • You are logging the name of the arrow function (parameter `entity`), which is the empty string, and the name of the property that the decorator was applied to (parameter `name`). – Bergi Oct 20 '19 at 21:19
  • Yes. Thanks, @Bergi, My bad. I have updated ManytoMany as `export function ManyToMany(entity=() => entity)`. which logs article and tag entity. – Prashant Biradar Oct 20 '19 at 21:56
  • 1
    I came with this solution in the end: https://stackblitz.com/edit/typescript-rhmdhq?file=index.ts . This is what I was expecting to see in the console – Ionel Lupu Oct 21 '19 at 07:17
  • I had similiar problem but I didn't find a solution: https://stackoverflow.com/questions/63953101/typescript-circular-dependency-on-decorator – gio Sep 19 '20 at 08:52