4

I currently have an issue with lazy laoded modules and services which are providedIn this lazy-loaded module.

I have a folder structure looking like this:

app
-> featureModule1 (lazy loaded)
-> featureModule2 (lazy loaded)
-->services
--->service1.ts

I want this service1.ts only be available in featureModule2. So i included @Injectable

// service.ts    
@Injectable({
   providedIn: featureModule2
})
export class service1 { /* some logic */ }

When i try to route to this lazy loaded module i get an console error:

// console    
ERROR Error: Uncaught (in promise): ReferenceError: Cannot access 'featureModule2' before initialization
    ReferenceError: Cannot access 'featureModule2' before initialization

My current lazy load route looks like this:

// app-routing.module.ts    
const routes: Routes = [
      {path: '', redirectTo: '', pathMatch: 'full'},
      { path: 'feature1', loadChildren: () => import('./feature1/feature1.module').then(m => m.featureModule1) },
      { path: 'feature2', loadChildren: () => import('./feature2/feature2.module').then(m => m.featureModule2) }
    ];

I tried to provide it in the module:

// featureModule1     
@NgModule({
 declarations: [
  featureComponent
 ],
 imports: [
   ...
 ],
 providers: [
  service1
 ]
})

That doesn't work.

And i tried to import the service1.ts directly a component (featureComponent).

// featureComponent
import { service1 } from '../featureModule2/services/service1.service';

@Component({
  ...
})
export class featureComponent {
  constructor(private service1: service1) { }

  ngOnInit(): void {
    this.service1.init();
  }
}

This approach lead to the same error message.

The only way to get around this issue at the moment is to create a "wrapper" module which imports all other modules with @Injectable services.

Is there a different way for this problem? The services in each module should not be injected in root or any because they should only be available in each featureModule.

6
  • using providers in the module config will work 101%. Why do you say it is not.? Commented Aug 18, 2021 at 13:36
  • it works unitl i use lazy loading in the router Commented Aug 18, 2021 at 13:37
  • So it works or it does not? You can remove @Injectable and try again. Commented Aug 18, 2021 at 13:39
  • No it doesn't work in lazy loaded modules. When i remove @Injectable i can inject the service whereever i want in the app. This is not what i try to achieve. Commented Aug 18, 2021 at 13:44
  • This is on older angular, but should be fine stackblitz.com/edit/… Commented Aug 18, 2021 at 13:56

2 Answers 2

1

You just have to declare it in providers section and it will work. I have used useFactory provider variant in order to clearly show that it is used in order to perform injection and angular is not simply new LazyService just in place. I will work with providers:[LazyService] as well (which is a 'class provider' I belive, equal to useClass:LazyService)

In lazy module

import {
  CustomerListComponent,
  LazyService
} from './customer-list/customer-list.component';

@NgModule({
  imports: [CommonModule, CustomersRoutingModule],
  declarations: [CustomerListComponent],
  providers: [
    {
      provide: LazyService,
      useFactory: () => new LazyService('proofThatItIsFromPRoviders')
    }
  ]
})
export class CustomersModule {}

in lazy component from the same module ("printsLazy service works proofThatItIsFromPRoviders")

export class LazyService {
  constructor(private test: string) {}
  public say() {
    console.log('Lazy service works', this.test);
  }
}

@Component({
  selector: 'app-customer-list',
  templateUrl: './customer-list.component.html',
  styleUrls: ['./customer-list.component.css']
})
export class CustomerListComponent implements OnInit {
  constructor(private lazy: LazyService) {}

  ngOnInit() {
    this.lazy.say();
  }
}

injection in different module fails

@Component({
  selector: 'app-order-list',
  templateUrl: './order-list.component.html',
  styleUrls: ['./order-list.component.css']
})
export class OrderListComponent implements OnInit {

  constructor(private lazy:LazyService) { }

  ngOnInit() {
    this.lazy.say();
  }

}

Working example here https://stackblitz.com/edit/angular-s5xxnb-j9ch1z?file=src%2Fapp%2Forders%2Forder-list%2Forder-list.component.ts

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

10 Comments

thats a good way to also lazy load the service inside but in this case it is still possible to inject this service somewhere else in the application.
My example shows that it is not possible - injection in different module fails at runtime.
You are absolutly right but if i also provide the lazyService in the orderComponent which is integrated in the orderModule it is possible to inject this service. stackblitz.com/edit/…
Yes, becouse you have "provided" it there - there is nothing strange about it. Angular and typescript does not provide isolation on the code level other than class being NOT exported at all. So you will be able to import it wherever you like. You will end up in multiple instances etc, but you cannot forbid compiler from doing so. Maybe forbiddenImports can do the trick in some case, but then is it worth of trying?
and right, keep in mind that in your case, you end up with 2 separate instances.
|
1

When we configure a service like this:

   @Injectable({
       providedIn: featureModule2
    })
    export class TestService {
    }

It means that TestService won't be available to applications unless they import featureModule2.

Since featureModule2 is a lazy loaded module, you definitely not want to specify this in app module imports and hence you will need to either use

     @Injectable({
          providedIn: 'any'
      })
      export class TestService {
      }

or

      @Injectable({
          providedIn: 'root'
      })
      export class TestService {
      }

The different between above two is that when we use 'root there will be a single instance of that service throught the app. In case of 'any' for lazily loaded module there will be a new instance of that service and for eagerly loaded module it will act as a singleton.

4 Comments

ok so your answer proved my tests above. So is it the correct way to use a module for lazy-loading and import in this module sub modules with restricted services? featureModule1(lazy) -> import: [featureModule1.1]; service1.ts providesIn: featureModule1.1
@MrDeibl: We should always provide your service in the root injector unless there is a case where we want that service to be available only if the consumer imports a particular NgModule.
Yes great thank you and this is what i want to achieve in this case. There are some services which should not be injectable in other modules.
All such services then should be provided to a specific module using provideIn and then whichever module needs services from this module must import this module.

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.