67

I am new to typescript and am trying to understand how I can setup a circular reference between two types. The reference need not be a full code reference, simply the interfaces, but with interfaces defined in separate files. For example, let's say I have two interfaces: Parent and Child. They are doubly-linked such that the parent has a collection of children and each child has a reference to the parent (as seen below). How do I setup the imports or dependencies so that these can be defined in separate files?

interface Parent {
  children: Child[]
}

interface Child {
  parent: Parent
}
Viper Bailey
  • 11,518
  • 5
  • 22
  • 33
  • 4
    I would personally put them in the same file because they are inextricably linked and are both erased during compilation. – Fenton Jun 27 '14 at 09:58
  • For ways to solve circular dependencies refer to this question: https://stackoverflow.com/a/62742533/3198983 – JesusIniesta Jul 05 '20 at 15:41

3 Answers3

45

I also faced with the similar situation.

I could resolve by using import type.

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html

mozu
  • 729
  • 1
  • 7
  • 10
  • 1
    This is exactly what I needed, thank you so much! – nhuesmann Mar 12 '21 at 19:10
  • A very elegant and quick fix. I try to always keep up with TS but missed this TS3.8 feature. Doesn't help that the description downplays its usefulness: `This feature is something most users may never have to think about; however, if you’ve hit issues under isolatedModules, TypeScript’s transpileModule API, or Babel, this feature might be relevant.` – Simon_Weaver Nov 29 '21 at 07:40
21

Two solutions below. I prefer the latter since it offers clean interfacing with Node JS modules, but unfortunately my IDE doesn't (yet) like it as much as I do...

Use references

Create a definitions.d.ts file that will only contain the references to your classes/interfaces

/// <reference path="Parent.ts" />
/// <reference path="Child.ts" />

In Parent.ts and Child.ts, point to a single reference, the definitions.d.ts file

/// <reference path="definitions.d.ts" />

Use import...require

pass the --module commonjs flag to tsc then import what you require and export what you want to expose

In Parent.ts

 import Child = require('Child')

 interface Parent { 
     children: Child[]
 }

 export = Parent

In Child.ts

 import Parent = require('Parent')
 
 interface Child {
     parent: Parent
 }

 export = Child

Please note, that you do not specify the extension '.ts' in require

David Villamizar
  • 802
  • 9
  • 16
Bruno Grieder
  • 28,128
  • 8
  • 69
  • 101
  • 28
    Your edit of Sept 2016 (still) generates circular dependency messages with typescript: >=2.4.2 <2.7.0 and tslint: ^5.13.1 – Youp Bernoulli Jun 14 '19 at 11:44
  • 3
    @BernoulliIT I removed the edit of 2016, which does not work anymore with more recent versions of typescript. The "solutions" above are still workarounds - a redesign is probably a better idea – Bruno Grieder Sep 26 '20 at 09:25
  • 3
    @BrunoGrieder what kind of a "redesign". For strongly typed languages cycles are normal and expected behavior! – Enerccio Sep 15 '21 at 14:54
  • The VSCode plugin 'Unused exports' also doesn't see d.ts as a circular dependency :) – FloodGames Jun 27 '22 at 14:05
  • The `Use import...require` approach worked for me! TypeScript version `4.5.5` . For some reason the `Use references` approach also resulted in a circular dependency. And the accepted answer did not allow me to do `let c: Child = new Child()` (in my case, Parent and Child were classes, not interfaces). – shikharraje Oct 18 '22 at 07:14
5

I have about 10 ts files , in a Circular-Dependency-Hell .

The common methods can't help me any more , because the dependencies relation between 10 files is to complex.

At finally , I solved it. Using following 2 methods :

  1. Install repo ———— "circular-dependency-plugin": "5.0.2"

    This repo will helps me to find the place where circular occurs.

  2. Using a designed internal.ts , to manage my import & export

    I tried the method of this article :

    How to fix nasty circular dependency issues once and for all in JavaScript & TypeScript

    This amazing article tells me to create internal.ts .

    and using like export * form 'file-A' ; export * from 'file-B' to manage my circular dependencies.

    It works very well when I use dependencies related to my 10 files, like this import classA from '../internal.ts'.

————————————————————————————————————

If the above method has no effect on you, I found another general solution:

Use

const File_Promise = import ('yourFilePath')" to import other file or module .

when you need to use this one, use

File_Promise.then (file => { file.xxx(file.yyy) }) , just like using Promise syntax. `

This will break the Circular-Dep Chain !

If i am you , I will continue this action until NO ERROR Reported by "circular-dependency-plugin".

————————————————————————————————————

Hope to help YOU !

Lancer.Yan
  • 857
  • 13
  • 10
  • 1
    I apply the same method but still im getting depencies warning like that src/app/shared/entities/internal.ts > src/app/shared/entities/calender-event.entity.ts src/app/shared/entities/internal.ts > src/app/shared/entities/contact.entity.ts 6) src/app/shared/entities/internal.ts > src/app/shared/entities/email-template.entity.ts 7) src/app/shared/entities/internal.ts > src/app/shared/entities/goal.entity.ts @Lancer.Yan – Sanam Apr 09 '20 at 21:39
  • @Sanam I found a general solution. Use "const File_Promise = import ('yourFilePath')", when you need to use the function of the original File, use File_Promise.then (file => {}), just like using Promise syntax. – Lancer.Yan Apr 10 '20 at 02:41