JME
JME

Reputation: 2363

Refactoring circular dependencies in Typescript

I have an API which denormalizes data causing circular dependency. Is there a way to refactor the following using abstract classes, interfaces, composition or other techniques where I wouldn't need to create N partial classes for each entity to avoid a circular dependency in my Angular application's type definitions?

model.ts

export abstract class Model {
  // ... Model related data members and functions
}

person.ts

import { Model } from './model';
import { Site } from './site';

export class Person extends Model {

  // ... Data

  site: Site; // Partially or Fully saturated site entity
}

site.ts

import { Model } from './model';
import { Person } from './person';

export class Site extends Model {

  // ... Data

  people: Person[]; // array of partially saturated people entities, site is left undefined
}

I would like to maintain a single definition for each model, instead of redefining Site, Person, etc each time there is a dependency like this.

Upvotes: 6

Views: 1114

Answers (2)

adrisons
adrisons

Reputation: 3723

This problem is caused by a bad class model design and it does not depend on the language. I hope this helps :)

The circular dependency problems generally include the chicken & egg problem, because when you want to instantiate an object you do not know which you should instantiate first.

 Solution 1

This problem can be easily solved by only referencing objects by their identities, and instead of having a direct dependency on a big object as a part of constructor. For example, modifying Person class:

import { Model } from './model';

export class Person extends Model {

  // ... Data

  siteId: number;
}

 Solution 2

If you need the full object inside Person, I think a good approach would be to change the inheritance tree. I understand that your data model is about a website, its users and its owner. Well, lets break this down: make Person an abstract class and extend it with User and Owner classes.

person.ts

import { Model } from './model';

export abstract class Person extends Model {

  // ... Data
  
  // Does not include the Site attribute!
}

user.ts

Users will be the "readers" of the Site, but don't need a reference to it.

import { Person } from './person';

export class User extends Person {

  // ... Data

}

owner.ts

Owner will be the creator of the Site. This is the class that has the reference to the Site.

import { Person } from './person';

export class Owner extends Person {

  // ... Data

  site: Site;

}

site.ts

Finally, the Site keeps track of its users.

import { Model } from './model';
import { User } from './user';

export class Site extends Model {

  // ... Data

  users: User[];
}

I think this related article could be of help for understanding circular dependencies: How to fix nasty circular dependency issues once and for all in JavaScript & TypeScript

Upvotes: 5

Kathak Dabhi
Kathak Dabhi

Reputation: 399

Why don't use internal module pattern which export all the class and used internally by models to load dependencies.

Example Application: https://codesandbox.io/s/circular-deps-fix-using-internal-module-pattern-mtfro

internal.ts

export * from './model'
export * from './person'
export * from './site'

model.ts

export abstract class Model {
  // ... Model related data members and functions
}

person.ts

import { Model, Site  } from './internal';

export class Person extends Model {

  // ... Data

  site: Site; // Partially or Fully saturated site entity
}

site.ts

import { Model, Person } from './internal';

export class Site extends Model {

  // ... Data

  people: Person[]; // array of partially saturated people entities, site is left undefined
}

the above rules only apply to our local dependencies. External module imports are left as is. They are not involved in our circular dependency problems after all.

Upvotes: 2

Related Questions