Angular 6 and above: The use of ProvidedIn in a submodule is leading to a circular dependency issue

A resolve service is being implemented using the new providedIn attribute.

This translations resolver is utilized in a protected module:

import { Injectable } from '@angular/core';

import { Observable , pipe } from 'rxjs';
import {map} from "rxjs/operators";

//This causes: "WARNING in Circular dependency detected:"
import {ProtectedModule} from "../../../protected/protected.module";

import { HttpHandlerService } from '../../http/http-handler.service';

@Injectable({
  providedIn: ProtectedModule //The import for this line is required
})
export class TranslationsResolverService {
  constructor(private _httpHandlerService : HttpHandlerService) { }
    resolve(): any {
      //Do Something...
    }
}

The translations resolver service is declared in the protected routing module:

import { NgModule }           from '@angular/core';
import {RouterModule, Routes} from '@angular/router';

import {AuthGuard} from "../core/resolvers/auth/auth.guard";
import {TranslationsResolverService} from "./../core/resolvers/translations/translations-resolver.service";

const routes: Routes = [
  {
    path : 'app' ,
    component: ProtectedComponent,
    resolve : {
      translations : TranslationsResolverService 
    },
    canActivate: [AuthGuard],
    ]
  }
];


@NgModule({
  imports : [RouterModule.forChild(routes)],
  exports : [RouterModule]
})
export class ProtectedRoutingModule { }

About importing the protected.module in the translations-resolver.service.ts to use it in the providedIn attribute, it triggers a WARNING about Circular dependency:

path/to/translations-resolver.service.ts -> 

protected/protected.module.ts ->

protected/protected-routing.module.ts -> 

path to translations-resolver.service.ts

The extra path (protected/protected.module.ts) is added due to the providedIn attribute.

To remedy this issue, the translationsResolver can be provided as an NgModule provider (in the providers array), but preference is towards it being an injectable provider.

Any suggestions on how to solve this?

Answer №1

Encountered the same issue recently. It seems that the solution is simply to avoid doing it altogether, as detailed in a discussion by one of the team members of Angular: https://github.com/angular/angular-cli/issues/10170#issuecomment-380673276

Apparently, providing services at the root module level makes them more efficiently tree shakeable.

The disappointment is mutual.

Answer №2

Latest Update - October 2019

After receiving 5 up-votes on this answer, I feel it's important to clarify that I have actually deviated from my initial advice mentioned below!

Given the widely accepted Angular policy of using providedIn: 'root', I made the decision to stick with this approach for the sake of clarity among other developers. So far, this choice has not caused any issues for me, but the concerns raised in my original post still hold true and should be kept in mind.

Original Post Overview

In my opinion, Angular has introduced some confusion with the syntax of providedIn, evident from the discussions seen in various GitHub threads:

The use of providedIn offers two primary advantages:

  1. It facilitates tree-shaking to eliminate unused services
  2. providedIn: 'root' guarantees a single instance of the service across the application

However, benefit (1) is most relevant when developing a library as opposed to an application, and issue (2) can be resolved by ensuring the service module is only imported once to prevent multiple service instances.

The challenges associated with the providedIn syntax include:

  1. providedIn: 'root' severs the connection between the service and its parent module, leading to potential bundling complexities where dependencies are concerned. This places the burden on the service consumer to manage injectable dependencies, which can be confusing and counterintuitive.
  2. A circular reference dilemma emerges if the service is utilized by components within the same module, as preserving the link becomes impossible via this syntax.

While going against official Angular recommendations, my suggestion would be: Avoid using providedIn unless you are creating a third-party library requiring tree-shaking capabilities. Instead, opt for the conventional providers syntax within the module like so:

@NgModule({ providers: [MyService], })

Answer №3

This issue is not related to Angular dependencies.

The circular reference arises from the TypeScript compiler encountering circular imports.

A Possible Solution

To tackle this, consider creating a separate module called ProtectedResolversModule. Then, use

providedIn: ProtectedResolversModule
and place the resolvers there.

You can then import this new module into ProtectedModule, which should help eliminate the circular dependency error when loading ProtectedRoutingModule.

Another Approach

An alternative solution could involve utilizing the providers array within ProtectedModule.

Answer №4

When working with Angular9+

You have the option to utilize providerIn: Any

Essentially, it functions similarly to a module, but without direct module usage to avoid circular dependency issues.

Documentation: https://angular.io/api/core/Injectable#options

'any' : Provides a distinct instance in each lazily loaded module, while all eagerly loaded modules share a single instance.

In essence, it operates within a separate injection tree. It differs from instances used in other modules.

For further references

https://dev.to/christiankohler/improved-dependeny-injection-with-the-new-providedin-scopes-any-and-platform-30bb

'Any' is beneficial for ensuring that a service remains a singleton within module boundaries, serving as a strong alternative to 'root' to prevent cross-module side effects.

Sample code snippet for providerIn

  private injectableDefInScope(def: ɵɵInjectableDef<any>): boolean {
    if (!def.providedIn) {
      return false;
    } else if (typeof def.providedIn === 'string') {
      return def.providedIn === 'any' || (def.providedIn === this.scope);
    } else {
      return this.injectorDefTypes.has(def.providedIn);
    }
  }

Answer №5

Explore the forwardRef() function in angular/core. This function enables referencing items that have not yet been defined.

import {AnotherService} from './another-service';

constructor(@Inject(forwardRef(() => AnotherService)) public anotherService: AnotherService) {
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue with populating virtual IDs in NestJS mongoose schema containing an array of schemas

In the schema provided below, I have defined the structure for Map, Marker, and Desk: export type MapDocument = Map & Document @Schema({ timestamps: true, versionKey: false, id: true }) export class Map { constructor(partial?: Partial< ...

Passing data from child to parent in Angular using EventEmitter

I have integrated a child grid component into my Angular application's parent component, and I am facing an issue with emitting data from the child to the parent. Despite using event emitter to transmit the value to the parent, the variable containing ...

The Uncaught SyntaxError issue arises when configuring webpack and Karma together

I am setting up webpack + karma + angular 2 and encountering a Uncaught SyntaxError : Unexpected token import. I am puzzled by the cause of this error. When I execute $karma start, it throws this error. Please assist me. Error START: webpack: bundle is ...

The Angular subscribe value is consistently assigned as -1, just before it receives the actual assigned value

Upon completion of the .subscribe, I am attempting to trigger an event as the subsequent code relies on the result. checkIfBordereauExists(id: string) { return this._BourdereauService.get_one(id).subscribe( data = > { if (data.i ...

Acquire key for object generated post push operation (using Angular with Firebase)

I'm running into some difficulties grasping the ins and outs of utilizing Firebase. I crafted a function to upload some data into my firebase database. My main concern is obtaining the Key that is generated after I successfully push the data into the ...

Utilizing custom types in React with TypeScript and Prop-Types

Currently, I am working with a string type literal that looks like this: type MyType = 'str1' | 'str2' | 'str3'. I need one of my props to only accept this specific type, but I'm struggling to specify this in PropTypes. ...

Trigger an event when an Angular template's *ngIf condition is met

Let's say I am using the Angular directive *ngIf to display a user object like so: <div *ngIf="user$ | async as user" class="container"> <p>{{user.name}}</p> </div> Is there a method where I can trigger some code once this ...

Is there a way to verify in Angular whether an image link has a width and height exceeding 1000?

I'm currently working on a function that checks if an image linked in an input field has a width and height greater than 1000 pixels, and is in JPG format. Here's my approach: HTML: <input (change)="checkValidImage(1, product.main_photo)" [ ...

Typescript's interface for key-value pairing with generic types

Consider the example Object below: let obj1: Obj = { 'key1': { default: 'hello', fn: (val:string) => val }, 'key2': { default: 123, fn: (val:number) => val }, // this should throw an error, because the types of d ...

React doesn't have file upload configured to update the state

I am working on integrating a file upload button that sends data to an API. To ensure only the button triggers the upload dialog and not the input field, I have set it up this way. Issue: The File is not being saved to state, preventing me from using a ...

Issue with RouterLink not recognizing QueryParams

I have encountered an issue where dynamically generated URLs with queryParams inside [routerLink] are breaking routes. For example: this.url = '/question/ask?details=1' <a [routerLink]="url"> {{ data.name }}</a> Upon mouseover, the ...

Transforming encoded information into a text format and then reversing the process

I am facing an issue with storing encrypted data in a string format. I have tried using the TextEncoder method but it seems to be creating strings with different bytes compared to the original ArrayBuffer. Here is the line causing the problem: const str ...

When conditional types are used to pass unions through generics, the assigned value defaults to 'any' instead of

In search of a universal type to implement in my actions. Actions can vary from simple functions to functions that return another function, demonstrated below: () => void () => (input: I) => void An Action type with a conditional generic Input h ...

What steps should I take to update the Angular CLI version?

I am currently struggling with the installation of Angular CLI version 15.2.8, despite my attempts to upgrade. Currently, I have the following version of Angular: https://i.stack.imgur.com/m477e.png In an effort to change the version, I have been runnin ...

An issue has been detected in the @angular/material/autocomplete/typings/autocomplete-origin.d.ts file: The type 'ElementRef' is not declared as a generic type

Recently, I downloaded an Angular template that utilizes the Angular Material library. While trying to run this template on my local machine, I successfully executed the npm install command. However, when attempting to run ng serve, I encountered several w ...

An unexpected error occurred in the Angular unit and integration tests, throwing off the script

I seem to be facing a recurring issue while running unit/integration tests for my Angular project using Karma. The tests have a 50:50 success/failure rate, working fine on my machine but failing consistently on our build server, making the process quite un ...

I am encountering difficulties when attempting to install a specific Angular project using npm install

My old laptop was able to run my Angular project smoothly, but now I'm encountering numerous errors when trying to install it with npm on a new device. Any assistance based on the attached log would be greatly appreciated. Thank you in advance. shiva ...

Obtain an array of column values within an array of objects using Angular 12

I am working on an Angular 12 project and need to fetch all data from the artisticBehaviour column in the Users table, excluding NULL values or duplicates e.g. Actor, Actor. Below is the TypeScript function that retrieves all users from the Users table. a ...

Unraveling the Mystery of Dependency Injection: My Struggle to Grasp the Concept

Currently diving into Angular 2 and stumbled upon a video that really shed some light on the topic for me: https://www.youtube.com/watch?v=_-CD_5YhJTA However, when it comes to dependency injection, there's a particular point Mosh brings up at the 36 ...

Utilizing Sequelize's Where clause with the flexibility of optional parameters

Can you guide me on writing Sequelize queries with optional parameters? Consider the below query example: const result : SomeModel[] = await SomeModel.findAll( {where: { id: givenId, ...