Skip to content

feat(core): more precise type for APP_INITIALIZER token #40986

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

abarghoud
Copy link
Contributor

@abarghoud abarghoud commented Feb 24, 2021

This commit updates the type of the APP_INITIALIZER injection token to
better document the expected types of values that Angular handles. Only
Promises and Observables are awaited and other types of values are ignored,
so the type of APP_INITIALIZER has been updated to
Promise<unknown> | Observable<unknown> | void to reflect this behavior.

BREAKING CHANGE:

The type of the APP_INITIALIZER token has been changed to more accurately
reflect the types of return values that are handled by Angular. Previously,
each initializer callback was typed to return any, this is now
Promise<unknown> | Observable<unknown> | void. In the unlikely event that
your application uses the Injector.get or TestBed.inject API to inject
the APP_INITIALIZER token, you may need to update the code to account for
the stricter type.

Additionally, TypeScript may report the TS2742 error if the APP_INITIALIZER
token is used in an expression of which its inferred type has to be emitted
into a .d.ts file. To workaround this, an explicit type annotation is needed,
which would typically be Provider or Provider[].

Closes #40729

Does this PR introduce a breaking change?

  • Yes
  • No

@google-cla google-cla bot added the cla: yes label Feb 24, 2021
@pullapprove pullapprove bot requested a review from mhevery February 24, 2021 19:40
@abarghoud abarghoud force-pushed the modify-app-initializer-token-type branch from 365122d to b0062df Compare February 24, 2021 20:24
@JoostK JoostK requested a review from AndrewKushnir February 24, 2021 20:39
@AndrewKushnir AndrewKushnir added action: review The PR is still awaiting reviews from at least one requested reviewer area: core Issues related to the framework runtime cross-cutting: types breaking changes target: major This PR is targeted for the next major release labels Feb 24, 2021
@ngbot ngbot bot modified the milestone: Backlog Feb 24, 2021
@abarghoud abarghoud force-pushed the modify-app-initializer-token-type branch 3 times, most recently from ac87478 to 40ab7b5 Compare February 24, 2021 21:12
@abarghoud
Copy link
Contributor Author

@JoostK do you have any idea of why tests are failing with the following error:

ERROR: /home/circleci/ng/packages/router/BUILD.bazel:5:10: Compiling Angular templates (ViewEngine - devmode) //packages/router:router failed: (Exit 1): ngc-wrapped.sh failed: error executing command 
  (cd /home/circleci/.cache/bazel/_bazel_circleci/9ce5c2144ecf75d11717c0aa41e45a8d/execroot/angular && \
  exec env - \
  bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.sh '--node_options=--expose-gc' '--node_options=--stack-trace-limit=100' '--node_options=--max-old-space-size=4096' @@bazel-out/k8-fastbuild/bin/packages/router/router_es5_tsconfig.json)
Execution platform: //tools:rbe_ubuntu1604-angular
packages/router/src/router_module.ts:587:17 - error TS2742: The inferred type of 'provideRouterInitializer' cannot be named without a reference to '../../../external/npm/node_modules/rxjs'. This is likely not portable. A type annotation is necessary.

587 export function provideRouterInitializer() {

@JoostK
Copy link
Member

JoostK commented Feb 24, 2021

@JoostK do you have any idea of why tests are failing with the following error:

ERROR: /home/circleci/ng/packages/router/BUILD.bazel:5:10: Compiling Angular templates (ViewEngine - devmode) //packages/router:router failed: (Exit 1): ngc-wrapped.sh failed: error executing command 
  (cd /home/circleci/.cache/bazel/_bazel_circleci/9ce5c2144ecf75d11717c0aa41e45a8d/execroot/angular && \
  exec env - \
  bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.sh '--node_options=--expose-gc' '--node_options=--stack-trace-limit=100' '--node_options=--max-old-space-size=4096' @@bazel-out/k8-fastbuild/bin/packages/router/router_es5_tsconfig.json)
Execution platform: //tools:rbe_ubuntu1604-angular
packages/router/src/router_module.ts:587:17 - error TS2742: The inferred type of 'provideRouterInitializer' cannot be named without a reference to '../../../external/npm/node_modules/rxjs'. This is likely not portable. A type annotation is necessary.

587 export function provideRouterInitializer() {

Yeah, maybe this is why @AndrewKushnir marked this as breaking?

The issue here is that TypeScript uses its inferred type from the returned array literal as the return type of the function declaration that is emitted into the generated .d.ts file. Since it includes APP_INITIALIZER, its type is present somewhere in the inferred return type and this would therefore have to emit Observable and create a valid import for it. TS is telling you that the import path it determined for rxjs is unlikely to portable, which is true as that relative import path may not be valid in a different context (e.g. after the file has been published to NPM).

export function provideRouterInitializer() {
return [
RouterInitializer,
{
provide: APP_INITIALIZER,
multi: true,
useFactory: getAppInitializer,
deps: [RouterInitializer]
},
{provide: ROUTER_INITIALIZER, useFactory: getBootstrapListener, deps: [RouterInitializer]},
{provide: APP_BOOTSTRAP_LISTENER, multi: true, useExisting: ROUTER_INITIALIZER},
];

The workaround is quite simple: add an explicit return type Provider[] to that function.

It's an interesting case considering the fact that this was not an issue with the named type; I'd be interested to hear what others think here w.r.t. having a named type as you initially had.

@abarghoud abarghoud force-pushed the modify-app-initializer-token-type branch from 40ab7b5 to 59bd7f3 Compare February 24, 2021 21:48
@pullapprove pullapprove bot requested a review from atscott February 24, 2021 21:48
@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented Feb 24, 2021

Yeah, maybe this is why @AndrewKushnir marked this as breaking?

I was under impression that this would be breaking and was going to perform some research on which scenarios can be affected, but tests identified them faster :)

It's an interesting case considering the fact that this was not an issue with the named type; I'd be interested to hear what others think here w.r.t. having a named type as you initially had.

I like the current implementation (without exposing additional type), I think it's a more "lightweigth" API. I've started a set of tests in Google's codebase (internal-only link) to see how that would affect apps (+ also started a global run). We can use that data as an input to the discussion and final decision.

Thank you.

@AndrewKushnir AndrewKushnir added the action: presubmit The PR is in need of a google3 presubmit label Feb 24, 2021
@AndrewKushnir
Copy link
Contributor

Quick update after running tests in Google's codebase (all affected targets aka global run): there are no breakages caused by this change, which suggests that this changes is not that break-y (but still this is a breaking change as we've seen it with a Router example).

Copy link
Contributor

@AndrewKushnir AndrewKushnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abarghoud I've left a couple suggestions to use ReadonlyArray instead of an Array, but overall the change looks good and it's not causing any issues in Google's codebase.

We'd also need to update commit message to include the "BREAKING CHANGE" note. We'll followup with a proposed draft to explain in which circumstances this might be an actual breaking change.

Thank you.

@JoostK
Copy link
Member

JoostK commented Feb 26, 2021

@abarghoud Here's the suggestion for the commit message:

feat(core): more precise type for `APP_INITIALIZER` token

This commit updates the type of the `APP_INITIALIZER` injection token to
better document the expected types of values that Angular handles. Only
Promises and Observables are awaited and other types of values are ignored,
so the type of `APP_INITIALIZER` has been updated to
`Promise<unknown> | Observable<unknown> | void` to reflect this behavior.

BREAKING CHANGE:

The type of the `APP_INITIALIZER` token has been changed to more accurately
reflect the types of return values that are handled by Angular. Previously,
each initializer callback was typed to return `any`, this is now
`Promise<unknown> | Observable<unknown> | void`. In the unlikely event that
your application uses the `Injector.get` or `TestBed.inject` API to inject
the `APP_INITIALIZER` token, you may need to update the code to account for
the stricter type.

Additionally, TypeScript may report the TS2742 error if the `APP_INITIALIZER`
token is used in an expression of which its inferred type has to be emitted
into a .d.ts file. To workaround this, an explicit type annotation is needed,
which would typically be `Provider` or `Provider[]`.

Closes #40729

@abarghoud abarghoud force-pushed the modify-app-initializer-token-type branch from 59bd7f3 to 90f4bd1 Compare February 28, 2021 07:15
@abarghoud
Copy link
Contributor Author

@JoostK @AndrewKushnir, I took into account your remarks as well as modified the commit message

This commit updates the type of the `APP_INITIALIZER` injection token to
better document the expected types of values that Angular handles. Only
Promises and Observables are awaited and other types of values are ignored,
so the type of `APP_INITIALIZER` has been updated to
`Promise<unknown> | Observable<unknown> | void` to reflect this behavior.

BREAKING CHANGE:

The type of the `APP_INITIALIZER` token has been changed to more accurately
reflect the types of return values that are handled by Angular. Previously,
each initializer callback was typed to return `any`, this is now
`Promise<unknown> | Observable<unknown> | void`. In the unlikely event that
your application uses the `Injector.get` or `TestBed.inject` API to inject
the `APP_INITIALIZER` token, you may need to update the code to account for
the stricter type.

Additionally, TypeScript may report the TS2742 error if the `APP_INITIALIZER`
token is used in an expression of which its inferred type has to be emitted
into a .d.ts file. To workaround this, an explicit type annotation is needed,
which would typically be `Provider` or `Provider[]`.

Closes angular#40729
@abarghoud abarghoud force-pushed the modify-app-initializer-token-type branch from 90f4bd1 to bc65f53 Compare February 28, 2021 07:17
@AndrewKushnir AndrewKushnir changed the title feat(core): More precise type for APP_INITIALIZER token feat(core): more precise type for APP_INITIALIZER token Feb 28, 2021
This was referenced Mar 18, 2021
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Apr 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime breaking changes cla: yes cross-cutting: types target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

More precise type for APP_INITIALIZER token
6 participants