Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to solve Circular Dependencies when using classes as Types in Typescript?

How can i use a typing in typescript that does not run into circular dependency errors?

It seems that the circular dependency error occurs, even though the imports should be removed when the code compiles to valid JS. Is that a bug?

user-model.ts

import { Post } from '../post-model'

export class User {
  Posts: Post[];
}

post-model.ts

import { User } from '../user-model'

export class Post {
  User: User;
}

I've heard about two possible solutions that both don't satisfy me.

One is, to create a new interface that matches the class: Circular dependency caused by importing typescript type

And I've read something in the docs of typegraphql: https://typegraphql.com/docs/types-and-fields.html

There they say:

Why use function syntax and not a simple { type: Rate } config object? Because, by using function syntax we solve the problem of circular dependencies (e.g. Post <--> User), so it was adopted as a convention. You can use the shorthand syntax @Field(() => Rate) if you want to save some keystrokes but it might be less readable for others.

I also didn't find any option to disable the circular dependency warning in typescript.

I'm working in Nrwl/Angular 9.x

like image 652
DevJules Avatar asked Feb 20 '26 09:02

DevJules


2 Answers

Another solution that does not use interfaces is to use type only imports.

user-model.ts

import type { Post } from './post-model'

export class User {
  Posts: Post[];
}

post-model.ts

import type { User } from './user-model'

export class Post {
  User: User;
}

These imports get completely removed upon compilation and serve only for the purpose of type checking - that means you can't use them as a value (you can't do new Post() with a type-only import for example).

I think this approach is cleaner and more DRY than the alternative that is creating a separate file with interfaces only for the sake of type checking.

like image 196
Papooch Avatar answered Feb 23 '26 00:02

Papooch


Using interfaces is the best option here. You can make a .d.ts for each and then import that instead.

user.d.ts

export interface IUser {
  Posts: IPost[];
}

post.d.ts

export interface IPost {
  User: IUser;
}

And then...

import { IPost, IUser } from './post.d'

export class User implements IUser {
  Posts: IPost[];
}
like image 42
Zze Avatar answered Feb 23 '26 02:02

Zze