Skip to content

fix(core): allow abstract class as injection token - #25222

Closed
trotyl wants to merge 8 commits into
angular:masterfrom
trotyl:fix-type-abstract-class
Closed

trotyl wants to merge 8 commits into
angular:masterfrom
trotyl:fix-type-abstract-class

Conversation

@trotyl

@trotyl trotyl commented Jul 31, 2018 •

Copy link
Copy Markdown
Contributor

closes #23611

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[x] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] angular.io application / infrastructure changes
[ ] Other... Please describe:

What is the current behavior?

Issue Number: #23611

There're many services used an abstract class as tokens, like:

After #13785 merged, injector.get(ChangeDetectorRef) would produce a deprecation warning since an abstract class is not a Type:

/**
* @deprecated from v4.0.0 use Type<T> or InjectionToken<T>
* @suppress {duplicate}
*/
abstract get(token: any, notFoundValue?: any): any;

Also in the new inject() API, it throws an error as no deprecated any fallback:

export function inject<T>(token: Type<T>| InjectionToken<T>): T;
export function inject<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|null;
export function inject<T>(token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {

What is the new behavior?

Abstract classes should be allowed as injection tokens, this PR has relaxed typing of Type so that abstract class can be now used.

Technically any non-class function can also pass the type now as they have an any prototype.

Does this PR introduce a breaking change?

[x] Yes
[ ] No

If one uses Type type and trying to new it directly.

Other information

@trotyl
trotyl force-pushed the fix-type-abstract-class branch from 45bbce3 to 86c6692 Compare July 31, 2018 14:07
@JoostK

JoostK commented Jul 31, 2018

Copy link
Copy Markdown
Member

Instead of relaxing the constraints of Type, we could also introduce a new type for Token in general. That would avoid the cast to Constructor for e.g. useClass providers. Furthermore, providers would not be able to incorrectly use an abstract class as value type (useClass or using the shortcut syntax of only the class), only as token (the provide property would be of type Token, same as inject's parameter).

@trotyl

trotyl commented Jul 31, 2018

Copy link
Copy Markdown
Contributor Author

@JoostK With providedIn usage, the type-to-construct is determined at consumer-side, not provider-side:

@Injectable({
  providedIn: 'root'
})
abstract class Foo { }

The newable or not of class Foo cannot be combined with Injectable options (extra provider or not), so there's no validation in the provider-side and consumer-side can only allow an abstract class without validation.

Then it will always be casting as newing an abstract class cannot be prevented.

@IgorMinar
IgorMinar requested a review from mhevery August 1, 2018 21:31
@IgorMinar IgorMinar added action: review The PR is still awaiting reviews from at least one requested reviewer area: core Issues related to the framework runtime labels Aug 1, 2018
Comment thread packages/core/src/application_ref.ts Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is the reasoning to change null to undefined?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

abstract create(
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
ngModule?: NgModuleRef<any>): ComponentRef<C>;

ComponentFactory#create() accepts NgModuleRef | undefined, not NgModuleRef | null, it's a compilation error in TypeScript.

Previously ngModule is of any type so bypassed the type-checking.

Comment thread packages/core/src/type.ts Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is a breaking change since we are changing the meaning of Type. I would much rather create AbstractType which is than extended by Type

interface AbstractType<T> extends Function { prototype: T; }
interface Type<T> extends AbstractType<T> { new (...args: any[]): T;}

I think that would work better and be less breaking. Many of the changes you had to do would not need to be made. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds reasonable, I'll have a try.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is indeed less breaking, but would let Type being completely meaningless, every API would only use AbstractType rather than Type.

AbstractType | Type is just AbstractType.

@googlebot

Copy link
Copy Markdown

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

@googlebot

Copy link
Copy Markdown

A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

@mhevery mhevery left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Created fixup for outstanding issues.

@mhevery

mhevery commented Sep 23, 2018

Copy link
Copy Markdown
Contributor

@googlebot

Copy link
Copy Markdown

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

@googlebot

Copy link
Copy Markdown

A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

@mhevery

mhevery commented Sep 23, 2018

Copy link
Copy Markdown
Contributor

@mhevery

mhevery commented Sep 23, 2018 •

Copy link
Copy Markdown
Contributor

There are a lot of breaking targets in Googl3 from this PR so this seems to be a breaking change. Needs more investigation.

javascript/angular2/testing/catalyst/helpers.ts:765:12 - error TS2352: Type 'typeof DynamicTemplateComponent' cannot be converted to type 'Type<T>'.
  Types of property 'prototype' are incompatible.
    Type 'DynamicTemplateComponent' is not comparable to type 'T'.

765     return DynamicTemplateComponent as Type<T>;

@trotyl

trotyl commented Sep 24, 2018 •

Copy link
Copy Markdown
Contributor Author

@mhevery The code return DynamicTemplateComponent as Type<T>; doesn't make sense, how would a non-generic concrete type be coerced to an opened generic type where T is defined by caller?

@mhevery
mhevery force-pushed the fix-type-abstract-class branch from b3fc729 to 3cdfda7 Compare October 2, 2018 05:06
@googlebot

Copy link
Copy Markdown

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

@googlebot googlebot removed the cla: yes label Oct 2, 2018
@googlebot

Copy link
Copy Markdown

A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

@IgorMinar IgorMinar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like this version of the PR, but I don't see any test that proves that abstract classes can actually be used as injection tokens. Can you please add one?

@IgorMinar

Copy link
Copy Markdown
Contributor

The PR also needs a rebase, which might uncover other compilation issues (many tests didn't run on CI). And I see that google3 has quite a few compilation failures as well.

The combination of all of these makes me more and more uncomfortable about landing this change now. If we can't make this a no/low developer pain change, we should wait with this until v8 and make a plan to roll it out.

@mhevery
mhevery force-pushed the fix-type-abstract-class branch from 575ee45 to 9045583 Compare October 3, 2018 16:41
@googlebot

Copy link
Copy Markdown

So there's good news and bad news.

👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there.

😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request.

Note to project maintainer: This is a terminal state, meaning the cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

@googlebot googlebot added cla: no and removed cla: yes labels Oct 3, 2018
@mhevery

mhevery commented Oct 3, 2018

Copy link
Copy Markdown
Contributor

Rebased and added a test.

@mhevery

mhevery commented Oct 3, 2018 •

Copy link
Copy Markdown
Contributor

Issues:

injector.get()

  /**
   * Retrieves an instance from the injector based on the provided token.
   * @returns The instance from the injector if defined, otherwise the `notFoundValue`.
   * @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
   */
  abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
  /**
   * @deprecated from v4.0.0 use Type<T> or InjectionToken<T>
   * @suppress {duplicate}
   */
  abstract get(token: any, notFoundValue?: any): any;

Notice that when an abstract type would be feed into get it would came back as any because the it would not match the Type. When we fix the get to accept AbstractType than we go form any to T on the return type this. breaks many call sites where developers are incorrectly taking advantage of any.

CDK wants ConcreteType.

Material2 CDK defines ComponentType as ConcreteType (which is correct because only concrete types can be dynamically loaded)

/** Interface that can be used to generically type a class. */
export interface ComponentType<T> {
  new (...args: any[]): T;
}

The issue is that if Angular changes Type to both AbstractType as well as ConcreteType it causes a type error since CDK's type is more restrictive than Angular's type.

Required Work To Land PR

  • Remove the deprecated method abstract get(token: any, notFoundValue?: any): any; from the Injector
    • Document the breaking change
    • Need automated refactoring in g3 to migrate
    • Create a schematic to automate migration for our users.
  • CDK ConcreteType
    • Document the breaking change as to why Type no longer can be assigned to ComponentType
    • Need automated refactoring in g3 to migrate
    • Create a schematic to automate migration for our users.

Conclusion

It is non-trivial amount of work and it is too late to land this for v7 and we will defer this for v8.

@AndrewKushnir

Copy link
Copy Markdown
Contributor

Hi, I'm closing this PR in favor of recently active #37958 that targets Ivy code. Thank you.

@angular-automatic-lock-bot

Copy link
Copy Markdown

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 Dec 19, 2020
@pullapprove pullapprove Bot added the area: compiler Issues related to `ngc`, Angular's template compiler label Dec 19, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: review The PR is still awaiting reviews from at least one requested reviewer area: compiler Issues related to `ngc`, Angular's template compiler area: core Issues related to the framework runtime breaking changes cla: no target: major This PR is targeted for the next major release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tree Shakable Providers and InjectionToken: inject() does not work with abstract classes

7 participants