angular: Treeshakeable Injector resolution logic doesn't work in a lazy loaded scenario

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
[ ] Other... Please describe:

Current behavior

When combining lazy-loading via the Router + the v6 providedIn: SomeModule syntax, the injector resolution step seems to fail.

Expected behavior

Should not throw an error

Minimal reproduction of the problem with instructions

See https://stackblitz.com/edit/angular-v6-providedin-4gxxoa?file=app/lazy.component.ts

Observe that:

  • AppModule uses the Router to lazy-load the LazyModule
  • LazyModule routes to a LazyComponent which injects two services: HelloService and GoodbyeService
  • HelloService is providedIn: 'root'
  • GoodbyeService is providedIn: LazyModule
  • Click the “Lazy” link to route to the LazyComponent
  • HelloService is successfully instantiated
  • GoodbyeService fails with ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[LazyComponent -> GoodbyeService]:

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 11
  • Comments: 17 (15 by maintainers)

Most upvoted comments

I see what you mean.

IMHO the new API is a nice thing b/c it makes default cases easier and allows for tree shaking. I also like the fact that I don’t need to update the module anymore. For more advanced use cases, we need to align with some patterns. They are not difficult, but one needs to know them.

I’ve written some things up, I ran into. Perhaps this is useful for you:

Blogged: The new Treeshakable Providers API in @Angular: Why, How And Cycles

http://www.softwarearchitekt.at/post/2018/05/06/the-new-treeshakable-providers-api-in-angular-why-how-and-cycles.aspx

@wKoza … ah, of course, you are right. 😄 I meant nothing to do in a sense of code that would allow to use syntax like providedIn: LazyModule.

As @manfredsteyer says, this is the result of a circular dependency between the component, service, and module.

Injector_ is I believe the node injector (for components, directives, etc). It shouldn’t resolve lazy providers, as you can’t say providedIn: SomeComponent. I’ll have to look why this doesn’t work with lazy loading, it’s likely a bug.

… but as I guess it’s not intended to instantiate GoodbyeService on app’s root and also it’s required not to use providers: [] section of LazyModule.

Yes @manfredsteyer , you can also put the GoodbyeService in the root module with providedIn: 'root'. GoodbyeService will be well in the chunk of the layModule

I ran into this too. The (one) reson for this seems to be that such a scenario causes a cycle:

GoodbyeService -------> LazyModule --------> LazyComponent
      ^                                            |
      +--------------------------------------------+

I think perhaps the doc should contain an example for this situation @alxhub

                             +---------------------------------+
                             |                                 |
                        LazyModule --------> LazyComponent     |
                                                   |           |
      +--------------------------------------------+           |
      |                                                        |
      V                                                        |
GoodbyeService --------> LazyServiceModule <-------------------+

@manfredsteyer Is the promise of tree-shakability worth the complexity at all? The old way of providing GoodbyeService via LazyModule’s providers isn’t deprecated, is it? <del>So if LazyComponent depends on GoodbyeService then GoodbyeService doesn’t fall off the tree, anyways. Because LazyComponent is used within the module’s declaration array and as such GoodbyeService is transitively used when injected into LazyComponent.</del> Edit Well it does fall off when only used at a type position in LazyComponent. Found my mistake.

In parts I see my concerns proved in how people try to systematically migrate their existing code base to tree-shakable providers using providedIn then running into issues with circular deps. I think the topic deserves an Appendix in the Dependency Injection docs.

Citing the docs

Unless there is a special case, the value should always be root. https://angular.io/guide/dependency-injection#tree-shakable-providers

Citing @hansl

providedIn: ‘root’ is the 99% of cases. https://github.com/angular/angular-cli/issues/10170.

I’ve discussed this with @alxhub the other day. We can resolve the cycle by putting the service in an own service module:

                             +---------------------------------+
                             |                                 |
                        LazyModule --------> LazyComponent     |
                                                   |           |
      +--------------------------------------------+           |
      |                                                        |
      V                                                        |
GoodbyeService --------> LazyServiceModule <-------------------+

Just tried it out. Works like a charm.