How to avoid Angular injectable instances duplication

I’m curious. What information does this add that’s not already covered with pretty much the same details in Tree-shakable dependencies in Angular projects?

We’ll add a link to your article from this one. Thanks for bringing it up!

1 Like

Basically I tried to solve and was focused on another kind of problem - instance duplication instead of ability to use tree-shaking. I mean that tree-shakable dependencies lead developers to smaller bundles, but instance duplication can lead to serious business-related bugs. But the technique is still the same. Thank you for bringing it up!

1 Like

Hello. First off, thank you guys for your articles. I’ve read both, the one related to this post discussion and another one by @LayZee which he referred to in his comment above. Hopefully, my question does not exceed the scope of the topic too much.

What I was wondering is how could tree-shakable providers manage to avoid cyclic dependency as opposed to pre-Angular 6 providers.

Here is my setup to show you what I mean:

@Injectable({
  providedIn: 'root',
  deps: [
    [MyService]
  ],
  useFactory: (instance: MyService) => instance || new MyService()
})
export class MyService {}

So far, with an Injector configured via providedIn property inside the Injectable decorator factory of my class everything is fine.

But once I switch to the pre-Angular 6 way of configuring injectors by registering my service in the providers: [], like there:

@NgModule({
  ...
  providers: [{
    deps: [
      [MyService]
    ],
    provide: MyService,
    useFactory: (instance: MyService) => instance || new MyService()
  }]
})
export class AppModule {}

I run into the following error:

Error: Provider parse errors:
Cannot instantiate cyclic dependency! MyService ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1

StackBlitz Demo

Hi divnych, how about adding an Optional decorator to the “parent” (existing) dependency?

1 Like

Is that what you mean, @LayZee?

@NgModule({
  ...
  providers: [{
    deps: [
      [new Optional(), MyService]
    ],
    provide: MyService,
    useFactory: (instance: MyService) => instance || new MyService()
  }]
})
export class AppModule {}

It didn’t make a difference.

That’s exactly what I meant. I will have to pull out an editor to try to help you out.

1 Like

Using the modern singleton service recipe from “Tree-shakable dependencies in Angular projects”, the following seems to work:

// app.module.ts
import { NgModule, Optional, SkipSelf } from '@angular/core';
// (...)
import { MyService } from './my.service';

@NgModule({
  // (...)
  providers: [
    {
      deps: [
        [new Optional(), new SkipSelf(), MyService],
      ],
      provide: MyService,
      useFactory: (instance: MyService | null): MyService =>
        instance || new MyService(),
    }
  ],
})
export class AppModule {}

SkipSelf does the trick.

1 Like

Indeed, that solves the problem. So basically, what is going on, as I understand, is that the first time MyService token is requested the provider invokes useFactory function, which in turn references MyService again, but this time trying to resolve and inject it into matching useFactory parameter. However, SkipSelf instructs DI to start resolution from the parent injector, which is the platform injector and where the provider is not found of course. Then, thanks to Optional decorator we get null returned and pass it to our useFactory function.

But how does tree-shakable DI handle it without Optional and SkipSelf couple? Interestingly enough, if I refer to the AppModule in providedIn option instead of 'root', like here:

@Injectable({
  deps: [
    [MyService]
  ],
  providedIn: AppModule,
  useFactory: (instance: MyService) => instance || new MyService()
})
export class MyService {}

I will get the following error:

Error: StaticInjectorError(AppModule)[AppComponent -> MyService]:
StaticInjectorError(Platform: core)[AppComponent -> MyService]:
NullInjectorError: No provider for MyService!

UPD: Also, I found an open issue on GitHub regarding it: https://github.com/angular/angular/issues/24082

@LayZee, thanks for answering and sorry if I wasted your time :slightly_smiling_face:

1 Like