feat(core): Add type information to injector.get() (#13785)

- Introduce `InjectionToken<T>` which is a parameterized and type-safe
  version of `OpaqueToken`.

DEPRECATION:
- `OpaqueToken` is now deprecated, use `InjectionToken<T>` instead.
- `Injector.get(token: any, notFoundValue?: any): any` is now deprecated
  use the same method which is now overloaded as
  `Injector.get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;`.

Migration
- Replace `OpaqueToken` with `InjectionToken<?>` and parameterize it.
- Migrate your code to only use `Type<?>` or `InjectionToken<?>` as
  injection tokens. Using other tokens will not be supported in the
  future.

BREAKING CHANGE:
- Because `injector.get()` is now parameterize it is possible that code
  which used to work no longer type checks. Example would be if one
  injects `Foo` but configures it as `{provide: Foo, useClass: MockFoo}`.
  The injection instance will be that of `MockFoo` but the type will be
  `Foo` instead of `any` as in the past. This means that it was possible
  to call a method on `MockFoo` in the past which now will fail type
  check. See this example:

```
class Foo {}
class MockFoo extends Foo {
  setupMock();
}

var PROVIDERS = [
  {provide: Foo, useClass: MockFoo}
];

...

function myTest(injector: Injector) {
  var foo = injector.get(Foo);
  // This line used to work since `foo` used to be `any` before this
  // change, it will now be `Foo`, and `Foo` does not have `setUpMock()`.
  // The fix is to downcast: `injector.get(Foo) as MockFoo`.
  foo.setUpMock();
}
```

PR Close #13785
This commit is contained in:
Miško Hevery
2017-01-03 16:54:46 -08:00
committed by Miško Hevery
parent 6d1f1a43bb
commit d169c2434e
59 changed files with 255 additions and 175 deletions

View File

@ -26,10 +26,42 @@
*
* Using an `OpaqueToken` is preferable to using an `Object` as tokens because it provides better
* error messages.
* @stable
* @deprecated since v4.0.0 because it does not support type information, use `InjectionToken<?>`
* instead.
*/
export class OpaqueToken {
constructor(private _desc: string) {}
constructor(protected _desc: string) {}
toString(): string { return `Token ${this._desc}`; }
}
/**
* Creates a token that can be used in a DI Provider.
*
* Use an `InjectionToken` whenever the type you are injecting is not reified (does not have a
* runtime representation) such as when injecting an interface, callable type, array or
* parametrized type.
*
* `InjectionToken` is parametrize on `T` which is the type of object which will be returned by the
* `Injector`. This provides additional level of type safety.
*
* ```
* interface MyInterface {...}
* var myInterface = injector.get(new InjectionToken<MyInterface>('SomeToken'));
* // myInterface is inferred to be MyInterface.
* ```
*
* ### Example
*
* {@example core/di/ts/injector_spec.ts region='Injector'}
*
* @stable
*/
export class InjectionToken<T> extends OpaqueToken {
// This unused property is needed here so that TS can differentiate InjectionToken from
// OpaqueToken since otherwise they would have the same shape and be treated as equivalent.
private _differentiate_from_OpaqueToken_structurally: any;
constructor(desc: string) { super(desc); }
toString(): string { return `InjectionToken ${this._desc}`; }
}

View File

@ -8,6 +8,9 @@
import {unimplemented} from '../facade/errors';
import {stringify} from '../facade/lang';
import {Type} from '../type';
import {InjectionToken} from './injection_token';
const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
@ -52,5 +55,10 @@ export abstract class Injector {
* Injector.THROW_IF_NOT_FOUND is given
* - Returns the `notFoundValue` otherwise
*/
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;
/**
* @deprecated from v4.0.0 use Type<T> or InjectToken<T>
*/
get(token: any, notFoundValue?: any): any;
get(token: any, notFoundValue?: any): any { return unimplemented(); }
}

View File

@ -52,7 +52,7 @@ export interface TypeProvider extends Type<any> {}
*/
export interface ValueProvider {
/**
* An injection token. (Typically an instance of `Type` or `OpaqueToken`, but can be `any`).
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
@ -96,7 +96,7 @@ export interface ValueProvider {
*/
export interface ClassProvider {
/**
* An injection token. (Typically an instance of `Type` or `OpaqueToken`, but can be `any`).
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
@ -134,7 +134,7 @@ export interface ClassProvider {
*/
export interface ExistingProvider {
/**
* An injection token. (Typically an instance of `Type` or `OpaqueToken`, but can be `any`).
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
@ -178,7 +178,7 @@ export interface ExistingProvider {
*/
export interface FactoryProvider {
/**
* An injection token. (Typically an instance of `Type` or `OpaqueToken`, but can be `any`).
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;