1

What is the best practice to avoid circular dependencies? I had this problem with Javascript but it also applies to any other programming language.

For example I have this translationService.ts module where whenever a locale is changed, I want to reload settings (so I need to import the settingsService.ts. But in my settingsService.ts I have a function reloadSettings which uses the translationService to translate the error message.

I guess you could say that the translationService should not know anything about the settingsService but how do you actually write the code to accomplish that?

translationService.ts

import { reloadSettings } from '@/services/settingsService.ts'

// When the locale is changed, we want to reload the settings
const changeLocale = (locale: string) => {
  i18n.locale = locale

  reloadSettings()
}

But in my settingsService.ts I have a dependency on the translationService in order to show the exception in the correct locale.

settingsService.ts

import settingsApi from '@/api/settingsApi.ts'
import { translate } from '@/services/translationService.ts'

const reloadSettings = () => {
  try {
    settingsStore.settings = settingsApi.getSettings()
  } catch (error) {
    console.log(
      translate(error.message)
    )
  }
}

So I end up with circular dependencies where settingsService depends on translationService and translationService depends on settingsService

settingsService -> translationService -> settingsService

8
  • The nice thing about Typescript is interfaces, you could define translation service as an interface, your implementation then does not need to worry about dependencies, as the interface is always the dependency. Yes, it one extra file, but it's much more maintainable / extendable.. Commented Jun 5, 2022 at 13:54
  • Could you give an example of that @Keith ? Commented Jun 5, 2022 at 13:57
  • @Keith: it's not only about the compilation but you'd also need a way to satisfy the interface dependency. There are multiple options, still, this must be discussed as part of this approach Commented Jun 5, 2022 at 14:15
  • Sorry, I'm only on mobile ATM, so not easy to show. But basically you create an interface for your service, this unit can then also store an instance, the other units can then register with this instance, once this is done circular dependencies are not a problem. There is a little bit more setup work, but it's much easier to maintaine especially as your app grows. Commented Jun 5, 2022 at 14:27
  • Why try to avoid them? The nice thing about ES6 modules is that this just works. Commented Jun 5, 2022 at 14:33

1 Answer 1

2

In most cases like this you should avoid the two-way coupling by removing at least one dependency and replace it with indirect coupling

Take your example.

But in my settingsService.ts I have a function reloadSettings which uses the translationService to translate the error message.

Sounds correct, your translation service sounds to be in lower level. Keep this dependency as direct.

I have this translationService.ts module where whenever a locale is changed, I want to reload settings (so I need to import the settingsService.ts)

No, you don't have to have a direct dependency here.

You can easily introduce an event emitter that just emits a locale-changed event and the settings service just subscribes to the event.

There's no direct dependency, both depend on the event emitter but still they communicate through events.

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

1 Comment

I did not think about that. That definitely sounds like one way to solve this problem.

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.