angular / angular

Deliver web apps with confidence 🚀
https://angular.dev
MIT License
96.23k stars 25.5k forks source link

inject() vs constructor injection inconsistency for Pipes #50952

Open Wintermoose opened 1 year ago

Wintermoose commented 1 year ago

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

No

Description

While converting our code to use the inject() function, I've noticed a change of behaviour that I didn't see documented anywhere. In our case, we're injecting pipe (MyPipe in repro) as a service , and the pipe itself injects another service (MyService). The catch here is that the pipe is provided on module level, while the service is provided on component level.

It seems that the constructor-based injection uses the directive injector (code inside MyService_factory), and as such finds MyService without problems. But when switching to inject() call, we end up with the module injectors and MyService is thus not found. In the repro you can see it when uncommenting line 16 and commenting out line 17.

I am actually not sure which behaviour is considered correct, all in all the inject() one is perhaps more consistent with the way services works, but I would still expect it to be identical in both cases. Alternatively it would be good to point it out in the documentation.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/angular-srjmnw?file=src%2Fmain.ts

Please provide the exception or error you saw

When swapping the injection in Pipe as described above:

NullInjectorError: R3InjectorError(AppModule)[MyPipe -> MyService -> MyService]: 
  NullInjectorError: No provider for MyService!

Please provide the environment you discovered this bug in (run ng version)

Angular 16.1.3

Anything else?

No response

JoostK commented 1 year ago

This is a bug with the constructor injection approach, using inject shows the expected behavior. Fixing it would likely be a breaking change, so we'd have to assess the impact of changing this behavior.

youssef3wi commented 1 year ago

Pipes do not possess an ElementInjector. I think if you do not have the provideIn option for this service, you cannot utilize it, or at the very least, you can only inject it in the "Host" (module).

This code will fail, without Host or provideIn.

constructor( private _service: MyService ) {}

By default, it is injectInjectorOnly, which makes it Injector-only aware. It can be changed to directiveInject, which brings in the NodeInjector system of ivy. It is designed this way for two reasons:

  1. Injector should not depend on ivy logic.
  2. To maintain tree shake-ability we don't want to bring in unnecessary code.