Skip to content

Commit

Permalink
feat(core): Add type information to injector.get() (angular#13785)
Browse files Browse the repository at this point in the history
- 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 angular#13785
  • Loading branch information
mhevery committed Jan 17, 2017
1 parent 1a3bb58 commit 92249f4
Show file tree
Hide file tree
Showing 59 changed files with 255 additions and 175 deletions.
2 changes: 1 addition & 1 deletion modules/@angular/benchpress/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';

export {Injector, OpaqueToken, Provider, ReflectiveInjector} from '@angular/core';
export {InjectionToken, Injector, Provider, ReflectiveInjector} from '@angular/core';
export {Options} from './src/common_options';
export {MeasureValues} from './src/measure_values';
export {Metric} from './src/metric';
Expand Down
32 changes: 16 additions & 16 deletions modules/@angular/benchpress/src/common_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@
* found in the LICENSE file at https://angular.io/license
*/

import {OpaqueToken} from '@angular/core';
import {InjectionToken} from '@angular/core';
import * as fs from 'fs';

export class Options {
static SAMPLE_ID = new OpaqueToken('Options.sampleId');
static DEFAULT_DESCRIPTION = new OpaqueToken('Options.defaultDescription');
static SAMPLE_DESCRIPTION = new OpaqueToken('Options.sampleDescription');
static FORCE_GC = new OpaqueToken('Options.forceGc');
static SAMPLE_ID = new InjectionToken('Options.sampleId');
static DEFAULT_DESCRIPTION = new InjectionToken('Options.defaultDescription');
static SAMPLE_DESCRIPTION = new InjectionToken('Options.sampleDescription');
static FORCE_GC = new InjectionToken('Options.forceGc');
static NO_PREPARE = () => true;
static PREPARE = new OpaqueToken('Options.prepare');
static EXECUTE = new OpaqueToken('Options.execute');
static CAPABILITIES = new OpaqueToken('Options.capabilities');
static USER_AGENT = new OpaqueToken('Options.userAgent');
static MICRO_METRICS = new OpaqueToken('Options.microMetrics');
static USER_METRICS = new OpaqueToken('Options.userMetrics');
static NOW = new OpaqueToken('Options.now');
static WRITE_FILE = new OpaqueToken('Options.writeFile');
static RECEIVED_DATA = new OpaqueToken('Options.receivedData');
static REQUEST_COUNT = new OpaqueToken('Options.requestCount');
static CAPTURE_FRAMES = new OpaqueToken('Options.frameCapture');
static PREPARE = new InjectionToken('Options.prepare');
static EXECUTE = new InjectionToken('Options.execute');
static CAPABILITIES = new InjectionToken('Options.capabilities');
static USER_AGENT = new InjectionToken('Options.userAgent');
static MICRO_METRICS = new InjectionToken('Options.microMetrics');
static USER_METRICS = new InjectionToken('Options.userMetrics');
static NOW = new InjectionToken('Options.now');
static WRITE_FILE = new InjectionToken('Options.writeFile');
static RECEIVED_DATA = new InjectionToken('Options.receivedData');
static REQUEST_COUNT = new InjectionToken('Options.requestCount');
static CAPTURE_FRAMES = new InjectionToken('Options.frameCapture');
static DEFAULT_PROVIDERS = [
{provide: Options.DEFAULT_DESCRIPTION, useValue: {}},
{provide: Options.SAMPLE_DESCRIPTION, useValue: {}},
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/benchpress/src/metric/multi_metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Injector, OpaqueToken} from '@angular/core';
import {InjectionToken, Injector} from '@angular/core';

import {Metric} from '../metric';

Expand Down Expand Up @@ -60,4 +60,4 @@ function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: strin
return result;
}

const _CHILDREN = new OpaqueToken('MultiMetric.children');
const _CHILDREN = new InjectionToken('MultiMetric.children');
4 changes: 2 additions & 2 deletions modules/@angular/benchpress/src/metric/perflog_metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Inject, Injectable, InjectionToken} from '@angular/core';

import {Options} from '../common_options';
import {Metric} from '../metric';
Expand All @@ -18,7 +18,7 @@ import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_e
*/
@Injectable()
export class PerflogMetric extends Metric {
static SET_TIMEOUT = new OpaqueToken('PerflogMetric.setTimeout');
static SET_TIMEOUT = new InjectionToken('PerflogMetric.setTimeout');
static PROVIDERS = [
PerflogMetric, {
provide: PerflogMetric.SET_TIMEOUT,
Expand Down
6 changes: 3 additions & 3 deletions modules/@angular/benchpress/src/reporter/console_reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Inject, Injectable, InjectionToken} from '@angular/core';
import {print} from '../facade/lang';
import {MeasureValues} from '../measure_values';
import {Reporter} from '../reporter';
Expand All @@ -20,8 +20,8 @@ import {formatNum, formatStats, sortedProps} from './util';
*/
@Injectable()
export class ConsoleReporter extends Reporter {
static PRINT = new OpaqueToken('ConsoleReporter.print');
static COLUMN_WIDTH = new OpaqueToken('ConsoleReporter.columnWidth');
static PRINT = new InjectionToken('ConsoleReporter.print');
static COLUMN_WIDTH = new InjectionToken('ConsoleReporter.columnWidth');
static PROVIDERS = [
ConsoleReporter, {provide: ConsoleReporter.COLUMN_WIDTH, useValue: 18},
{provide: ConsoleReporter.PRINT, useValue: print}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Inject, Injectable, InjectionToken} from '@angular/core';

import {Options} from '../common_options';
import {MeasureValues} from '../measure_values';
Expand All @@ -21,7 +21,7 @@ import {formatStats, sortedProps} from './util';
*/
@Injectable()
export class JsonFileReporter extends Reporter {
static PATH = new OpaqueToken('JsonFileReporter.path');
static PATH = new InjectionToken('JsonFileReporter.path');
static PROVIDERS = [JsonFileReporter, {provide: JsonFileReporter.PATH, useValue: '.'}];

constructor(
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/benchpress/src/reporter/multi_reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Injector, OpaqueToken} from '@angular/core';
import {InjectionToken, Injector} from '@angular/core';

import {MeasureValues} from '../measure_values';
import {Reporter} from '../reporter';
Expand Down Expand Up @@ -39,4 +39,4 @@ export class MultiReporter extends Reporter {
}
}

const _CHILDREN = new OpaqueToken('MultiReporter.children');
const _CHILDREN = new InjectionToken('MultiReporter.children');
2 changes: 1 addition & 1 deletion modules/@angular/benchpress/src/sample_description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {OpaqueToken} from '@angular/core';
import {InjectionToken} from '@angular/core';

import {Options} from './common_options';
import {Metric} from './metric';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Inject, Injectable, InjectionToken} from '@angular/core';

import {MeasureValues} from '../measure_values';
import {Statistic} from '../statistic';
Expand All @@ -18,8 +18,8 @@ import {Validator} from '../validator';
*/
@Injectable()
export class RegressionSlopeValidator extends Validator {
static SAMPLE_SIZE = new OpaqueToken('RegressionSlopeValidator.sampleSize');
static METRIC = new OpaqueToken('RegressionSlopeValidator.metric');
static SAMPLE_SIZE = new InjectionToken('RegressionSlopeValidator.sampleSize');
static METRIC = new InjectionToken('RegressionSlopeValidator.metric');
static PROVIDERS = [
RegressionSlopeValidator, {provide: RegressionSlopeValidator.SAMPLE_SIZE, useValue: 10},
{provide: RegressionSlopeValidator.METRIC, useValue: 'scriptTime'}
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/benchpress/src/validator/size_validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Inject, Injectable, InjectionToken} from '@angular/core';

import {MeasureValues} from '../measure_values';
import {Validator} from '../validator';
Expand All @@ -16,7 +16,7 @@ import {Validator} from '../validator';
*/
@Injectable()
export class SizeValidator extends Validator {
static SAMPLE_SIZE = new OpaqueToken('SizeValidator.sampleSize');
static SAMPLE_SIZE = new InjectionToken('SizeValidator.sampleSize');
static PROVIDERS = [SizeValidator, {provide: SizeValidator.SAMPLE_SIZE, useValue: 10}];

constructor(@Inject(SizeValidator.SAMPLE_SIZE) private _sampleSize: number) { super(); }
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/benchpress/src/web_driver_extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Injector, OpaqueToken} from '@angular/core';
import {InjectionToken, Injector} from '@angular/core';

import {Options} from './common_options';

Expand Down Expand Up @@ -101,4 +101,4 @@ export class PerfLogFeatures {
}
}

const _CHILDREN = new OpaqueToken('WebDriverExtension.children');
const _CHILDREN = new InjectionToken('WebDriverExtension.children');
4 changes: 2 additions & 2 deletions modules/@angular/common/src/location/location_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {OpaqueToken} from '@angular/core';
import {InjectionToken} from '@angular/core';
import {LocationChangeListener} from './platform_location';

/**
Expand Down Expand Up @@ -61,4 +61,4 @@ export abstract class LocationStrategy {
*
* @stable
*/
export const APP_BASE_HREF: OpaqueToken = new OpaqueToken('appBaseHref');
export const APP_BASE_HREF = new InjectionToken<string>('appBaseHref');
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {CommonModule} from '@angular/common';
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
import {Component, ComponentRef, Inject, Injector, NO_ERRORS_SCHEMA, NgModule, OpaqueToken, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {Component, ComponentRef, Inject, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';

Expand Down Expand Up @@ -146,7 +146,7 @@ export function main() {
});
}

const TEST_TOKEN = new OpaqueToken('TestToken');
const TEST_TOKEN = new InjectionToken('TestToken');
@Component({selector: 'injected-component', template: 'foo'})
class InjectedComponent {
constructor(@Optional() @Inject(TEST_TOKEN) public testToken: any) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Inject, OpaqueToken} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Inject, InjectionToken} from '@angular/core';

import {BasicComp} from './basic';

Expand All @@ -15,7 +15,7 @@ export class CompWithEntryComponents {
constructor(public cfr: ComponentFactoryResolver) {}
}

export const SOME_TOKEN = new OpaqueToken('someToken');
export const SOME_TOKEN = new InjectionToken('someToken');

export function provideValueWithEntryComponents(value: any) {
return [
Expand Down
8 changes: 4 additions & 4 deletions modules/@angular/compiler-cli/integrationtest/src/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
*/

import * as common from '@angular/common';
import {CUSTOM_ELEMENTS_SCHEMA, Component, Directive, EventEmitter, Inject, NgModule, OpaqueToken, Output} from '@angular/core';
import {CUSTOM_ELEMENTS_SCHEMA, Component, Directive, EventEmitter, Inject, InjectionToken, NgModule, Output} from '@angular/core';
import {Observable} from 'rxjs/Observable';

import {wrapInArray} from './funcs';

export const SOME_OPAQUE_TOKEN = new OpaqueToken('opaqueToken');
export const SOME_INJECTON_TOKEN = new InjectionToken('injectionToken');

@Component({
selector: 'comp-providers',
template: '',
providers: [
{provide: 'strToken', useValue: 'strValue'},
{provide: SOME_OPAQUE_TOKEN, useValue: 10},
{provide: SOME_INJECTON_TOKEN, useValue: 10},
{provide: 'reference', useValue: common.NgIf},
{provide: 'complexToken', useValue: {a: 1, b: ['test', SOME_OPAQUE_TOKEN]}},
{provide: 'complexToken', useValue: {a: 1, b: ['test', SOME_INJECTON_TOKEN]}},
]
})
export class CompWithProviders {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {LowerCasePipe, NgIf} from '@angular/common';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Directive, Inject, Injectable, Input, ModuleWithProviders, NgModule, OpaqueToken, Pipe} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Directive, Inject, Injectable, InjectionToken, Input, ModuleWithProviders, NgModule, Pipe} from '@angular/core';

@Injectable()
export class SomeService {
Expand Down Expand Up @@ -48,7 +48,7 @@ export class CompUsingRootModuleDirectiveAndPipe {
export class CompUsingLibModuleDirectiveAndPipe {
}

export const SOME_TOKEN = new OpaqueToken('someToken');
export const SOME_TOKEN = new InjectionToken('someToken');

export function provideValueWithEntryComponents(value: any) {
return [
Expand Down
22 changes: 13 additions & 9 deletions modules/@angular/compiler/src/aot/static_reflector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
diMetadata: '@angular/core/src/di/metadata',
diOpaqueToken: '@angular/core/src/di/opaque_token',
diInjectionToken: '@angular/core/src/di/injection_token',
diOpaqueToken: '@angular/core/src/di/injection_token',
animationMetadata: '@angular/core/src/animation/metadata',
provider: '@angular/core/src/di/provider'
};
Expand All @@ -34,6 +35,7 @@ export class StaticReflector implements ReflectorReader {
private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private injectionToken: StaticSymbol;
private opaqueToken: StaticSymbol;

constructor(
Expand Down Expand Up @@ -229,9 +231,10 @@ export class StaticReflector implements ReflectorReader {
}

private initializeConversionMap(): void {
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
ANGULAR_IMPORT_LOCATIONS;
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
const {coreDecorators, diDecorators, diMetadata, diInjectionToken,
diOpaqueToken, animationMetadata, provider} = ANGULAR_IMPORT_LOCATIONS;
this.injectionToken = this.findDeclaration(diInjectionToken, 'InjectionToken');
this.opaqueToken = this.findDeclaration(diInjectionToken, 'OpaqueToken');

this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
this._registerDecoratorOrConstructor(
Expand Down Expand Up @@ -382,7 +385,8 @@ export class StaticReflector implements ReflectorReader {
}
if (expression instanceof StaticSymbol) {
// Stop simplification at builtin symbols
if (expression === self.opaqueToken || self.conversionMap.has(expression)) {
if (expression === self.injectionToken || expression === self.opaqueToken ||
self.conversionMap.has(expression)) {
return expression;
} else {
const staticSymbol = expression;
Expand Down Expand Up @@ -506,9 +510,9 @@ export class StaticReflector implements ReflectorReader {
// Determine if the function is a built-in conversion
staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
if (staticSymbol instanceof StaticSymbol) {
if (staticSymbol === self.opaqueToken) {
// if somebody calls new OpaqueToken, don't create an OpaqueToken,
// but rather return the symbol to which the OpaqueToken is assigned to.
if (staticSymbol === self.injectionToken || staticSymbol === self.opaqueToken) {
// if somebody calls new InjectionToken, don't create an InjectionToken,
// but rather return the symbol to which the InjectionToken is assigned to.
return context;
}
const argExpressions: any[] = expression['arguments'] || [];
Expand Down Expand Up @@ -674,4 +678,4 @@ function positionalError(message: string, fileName: string, line: number, column
(result as any).line = line;
(result as any).column = column;
return result;
}
}
4 changes: 2 additions & 2 deletions modules/@angular/compiler/src/jit/compiler_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, OpaqueToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, InjectionToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';

import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config';
Expand Down Expand Up @@ -40,7 +40,7 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
`No ResourceLoader implementation has been provided. Can't read the url "${url}"`);}
};

const baseHtmlParser = new OpaqueToken('HtmlParser');
const baseHtmlParser = new InjectionToken('HtmlParser');

/**
* A set of providers that provide `JitCompiler` and its dependencies to use for
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/compiler/src/metadata_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, ModuleWithProviders, OpaqueToken, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, InjectionToken, ModuleWithProviders, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';

import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol';
import {ngfactoryFilePath} from './aot/util';
Expand All @@ -27,7 +27,7 @@ import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, SyntaxError, ValueTransformer, visitValue} from './util';

export type ErrorCollector = (error: any, type?: any) => void;
export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
export const ERROR_COLLECTOR_TOKEN = new InjectionToken('ErrorCollector');

// Design notes:
// - don't lazily create metadata:
Expand Down
Loading

0 comments on commit 92249f4

Please sign in to comment.