Skip to content

Commit

Permalink
fix(di): add better type information to injector.get()
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

- `injector.get()` is now parameterize and returns a type based on args.
  - `injector.get(Foo)` returns an instance of `Foo`
  - `injector.get(new OpaqueToken<Foo>(‘Foo’))` returns an instance of
    `Foo`.
- OpaqueToken is now parameterized.

Migration
- If you declare your own `OpaqueToken`, it must now be parameterized.
- In the past it was possible that the injector token could return a
  type other than itself. For example
  `var cars: Car[] = injector.get(Car)` because the provider for `Car`
  was declared `multi: true`. This is now no longer possible and it is
  considered a bad form. Proper way to do this is through
  `var CARS = new OpaqueToken<Car[]>;` and then
  `var cars = injector.get(CARS)`.
- `var foo: any = injector.get(‘something’)` is still supported and
  results in `any` type, but it’s use is strongly discouraged.
  • Loading branch information
mhevery committed Jan 4, 2017
1 parent 35f9a1c commit eea2e36
Show file tree
Hide file tree
Showing 17 changed files with 40 additions and 33 deletions.
2 changes: 1 addition & 1 deletion modules/@angular/common/src/location/location_strategy.ts
Original file line number Diff line number Diff line change
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 OpaqueToken<string>('appBaseHref');
3 changes: 2 additions & 1 deletion modules/@angular/compiler/src/url_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/

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

import {isBlank, isPresent} from './facade/lang';
import {CompilerInjectable} from './injectable';



/**
* Create a {@link UrlResolver} with no package prefix.
*/
Expand Down
7 changes: 3 additions & 4 deletions modules/@angular/core/src/application_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function createPlatform(injector: Injector): PlatformRef {
'There can be only one platform. Destroy the previous one to create a new one.');
}
_platform = injector.get(PlatformRef);
const inits: Function[] = <Function[]>injector.get(PLATFORM_INITIALIZER, null);
const inits = injector.get(PLATFORM_INITIALIZER, null);
if (inits) inits.forEach(init => init());
return _platform;
}
Expand Down Expand Up @@ -413,7 +413,7 @@ export class ApplicationRef_ extends ApplicationRef {
/** @internal */
static _tickScope: WtfScopeFn = wtfCreateScope('ApplicationRef#tick()');

private _bootstrapListeners: Function[] = [];
private _bootstrapListeners: ((compRef: ComponentRef<any>) => void)[] = [];
private _rootComponents: ComponentRef<any>[] = [];
private _rootComponentTypes: Type<any>[] = [];
private _views: AppView<any>[] = [];
Expand Down Expand Up @@ -480,8 +480,7 @@ export class ApplicationRef_ extends ApplicationRef {
this._rootComponents.push(componentRef);
// Get the listeners lazily to prevent DI cycles.
const listeners =
<((compRef: ComponentRef<any>) => void)[]>this._injector.get(APP_BOOTSTRAP_LISTENER, [])
.concat(this._bootstrapListeners);
this._injector.get(APP_BOOTSTRAP_LISTENER, []).concat(this._bootstrapListeners);
listeners.forEach((listener) => listener(componentRef));
}

Expand Down
8 changes: 5 additions & 3 deletions modules/@angular/core/src/application_tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {OpaqueToken} from './di';
import {ComponentRef} from './linker/component_factory';


/**
Expand Down Expand Up @@ -43,7 +44,7 @@ function _randomChar(): string {
* A function that will be executed when a platform is initialized.
* @experimental
*/
export const PLATFORM_INITIALIZER: any = new OpaqueToken('Platform Initializer');
export const PLATFORM_INITIALIZER = new OpaqueToken<(() => void)[]>('Platform Initializer');

/**
* All callbacks provided via this token will be called for every component that is bootstrapped.
Expand All @@ -53,10 +54,11 @@ export const PLATFORM_INITIALIZER: any = new OpaqueToken('Platform Initializer')
*
* @experimental
*/
export const APP_BOOTSTRAP_LISTENER = new OpaqueToken('appBootstrapListener');
export const APP_BOOTSTRAP_LISTENER =
new OpaqueToken<((compRef: ComponentRef<any>) => void)[]>('appBootstrapListener');

/**
* A token which indicates the root directory of the application
* @experimental
*/
export const PACKAGE_ROOT_URL: any = new OpaqueToken('Application Packages Root URL');
export const PACKAGE_ROOT_URL = new OpaqueToken<string>('Application Packages Root URL');
5 changes: 5 additions & 0 deletions modules/@angular/core/src/di/injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

import {unimplemented} from '../facade/errors';
import {stringify} from '../facade/lang';
import {Type} from '../type';

import {OpaqueToken} from './opaque_token';

const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
Expand Down Expand Up @@ -52,5 +55,7 @@ export abstract class Injector {
* Injector.THROW_IF_NOT_FOUND is given
* - Returns the `notFoundValue` otherwise
*/
get<T>(token: Type<T>|OpaqueToken<T>, notFoundValue?: T): T;
get(token: any, notFoundValue?: any): any;
get(token: any, notFoundValue?: any): any { return unimplemented(); }
}
2 changes: 1 addition & 1 deletion modules/@angular/core/src/di/opaque_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* error messages.
* @stable
*/
export class OpaqueToken {
export class OpaqueToken<T> {
constructor(private _desc: string) {}

toString(): string { return `Token ${this._desc}`; }
Expand Down
13 changes: 7 additions & 6 deletions modules/@angular/core/test/linker/ng_module_integration_spec.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 {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, Injector, Input, NgModule, NgModuleRef, OpaqueToken, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory} from '@angular/core';
import {Console} from '@angular/core/src/console';
import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
Expand All @@ -30,6 +30,7 @@ class Dashboard {

class TurboEngine extends Engine {}

const CARS = new OpaqueToken<Car[]>('Cars');
@Injectable()
class Car {
engine: Engine;
Expand Down Expand Up @@ -692,21 +693,21 @@ function declareTests({useJit}: {useJit: boolean}) {

it('should support multiProviders', () => {
const injector = createInjector([
Engine, {provide: Car, useClass: SportsCar, multi: true},
{provide: Car, useClass: CarWithOptionalEngine, multi: true}
Engine, {provide: CARS, useClass: SportsCar, multi: true},
{provide: CARS, useClass: CarWithOptionalEngine, multi: true}
]);

const cars = injector.get(Car);
const cars = injector.get(CARS);
expect(cars.length).toEqual(2);
expect(cars[0]).toBeAnInstanceOf(SportsCar);
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
});

it('should support multiProviders that are created using useExisting', () => {
const injector = createInjector(
[Engine, SportsCar, {provide: Car, useExisting: SportsCar, multi: true}]);
[Engine, SportsCar, {provide: CARS, useExisting: SportsCar, multi: true}]);

const cars = injector.get(Car);
const cars = injector.get(CARS);
expect(cars.length).toEqual(1);
expect(cars[0]).toBe(injector.get(SportsCar));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ export interface ControlValueAccessor {
* See {@link DefaultValueAccessor} for how to implement one.
* @stable
*/
export const NG_VALUE_ACCESSOR: OpaqueToken = new OpaqueToken('NgValueAccessor');
export const NG_VALUE_ACCESSOR = new OpaqueToken<ControlValueAccessor>('NgValueAccessor');
5 changes: 2 additions & 3 deletions modules/@angular/forms/src/directives/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,11 @@ function _throwError(dir: AbstractControlDirective, message: string): void {
throw new Error(`${message} ${messageEnd}`);
}

export function composeValidators(validators: /* Array<Validator|Function> */ any[]): ValidatorFn {
export function composeValidators(validators: Array<Validator|Function>): ValidatorFn {
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
}

export function composeAsyncValidators(validators: /* Array<Validator|Function> */ any[]):
AsyncValidatorFn {
export function composeAsyncValidators(validators: Array<Validator|Function>): AsyncValidatorFn {
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
null;
}
Expand Down
6 changes: 3 additions & 3 deletions modules/@angular/forms/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {OpaqueToken} from '@angular/core';
import {toPromise} from 'rxjs/operator/toPromise';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {AsyncValidatorFn, ValidatorFn, Validator} from './directives/validators';
import {StringMapWrapper} from './facade/collection';
import {isPresent} from './facade/lang';
import {AbstractControl} from './model';
Expand All @@ -28,7 +28,7 @@ function isEmptyInputValue(value: any) {
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
* @stable
*/
export const NG_VALIDATORS: OpaqueToken = new OpaqueToken('NgValidators');
export const NG_VALIDATORS = new OpaqueToken<Array<Validator|Function>>('NgValidators');

/**
* Providers for asynchronous validators to be used for {@link FormControl}s
Expand All @@ -40,7 +40,7 @@ export const NG_VALIDATORS: OpaqueToken = new OpaqueToken('NgValidators');
*
* @stable
*/
export const NG_ASYNC_VALIDATORS: OpaqueToken = new OpaqueToken('NgAsyncValidators');
export const NG_ASYNC_VALIDATORS = new OpaqueToken<Array<Validator|Function>>('NgAsyncValidators');

/**
* Provides a set of validators used by form controls.
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/http/test/http_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export function main() {

http = injector.get(Http);
jsonp = injector.get(Jsonp);
jsonpBackend = injector.get(JSONPBackend);
xhrBackend = injector.get(XHRBackend);
jsonpBackend = injector.get(JSONPBackend) as MockBackend;
xhrBackend = injector.get(XHRBackend) as any as MockBackend;

let xhrCreatedConnections = 0;
let jsonpCreatedConnections = 0;
Expand Down
2 changes: 1 addition & 1 deletion modules/@angular/platform-browser/src/dom/dom_tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ import {OpaqueToken} from '@angular/core';
*
* @stable
*/
export const DOCUMENT: OpaqueToken = new OpaqueToken('DocumentToken');
export const DOCUMENT = new OpaqueToken<Document>('DocumentToken');
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {getDOM} from '../dom_adapter';
/**
* @stable
*/
export const EVENT_MANAGER_PLUGINS: OpaqueToken = new OpaqueToken('EventManagerPlugins');
export const EVENT_MANAGER_PLUGINS = new OpaqueToken<EventManagerPlugin[]>('EventManagerPlugins');

/**
* @stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const EVENT_NAMES = {
*
* @experimental
*/
export const HAMMER_GESTURE_CONFIG: OpaqueToken = new OpaqueToken('HammerGestureConfig');
export const HAMMER_GESTURE_CONFIG = new OpaqueToken<HammerGestureConfig>('HammerGestureConfig');

export interface HammerInstance {
on(eventName: string, callback: Function): void;
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/platform-webworker/src/worker_render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class WebWorkerInstance {
/**
* @experimental WebWorker support is currently experimental.
*/
export const WORKER_SCRIPT: OpaqueToken = new OpaqueToken('WebWorkerScript');
export const WORKER_SCRIPT = new OpaqueToken<string>('WebWorkerScript');

/**
* A multi-provider used to automatically call the `start()` method after the service is
Expand All @@ -52,7 +52,7 @@ export const WORKER_SCRIPT: OpaqueToken = new OpaqueToken('WebWorkerScript');
* @experimental WebWorker support is currently experimental.
*/
export const WORKER_UI_STARTABLE_MESSAGING_SERVICE =
new OpaqueToken('WorkerRenderStartableMsgService');
new OpaqueToken<MessageBasedRenderer[]>('WorkerRenderStartableMsgService');

export const _WORKER_UI_PLATFORM_PROVIDERS: Provider[] = [
{provide: NgZone, useFactory: createNgZone, deps: []},
Expand Down
2 changes: 1 addition & 1 deletion modules/@angular/router/src/router_config_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {flatten, wrapIntoObservable} from './utils/collection';
/**
* @experimental
*/
export const ROUTES = new OpaqueToken('ROUTES');
export const ROUTES = new OpaqueToken<Route[][]>('ROUTES');

export class LoadedRouterConfig {
constructor(
Expand Down
4 changes: 2 additions & 2 deletions modules/@angular/router/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function initialRouterNavigation(
const router = ngUpgrade.injector.get(Router);
const ref = ngUpgrade.injector.get(ApplicationRef);

router.resetRootComponentType(ref.componentTypes[0]);
(router as any).resetRootComponentType(ref.componentTypes[0]);
preloader.setUpPreloading();
if (opts.initialNavigation === false) {
router.setUpLocationChangeListener();
Expand Down Expand Up @@ -86,4 +86,4 @@ export function setUpLocationSync(ngUpgrade: UpgradeModule): void {
url.href = next;
router.navigateByUrl(url.pathname);
});
}
}

0 comments on commit eea2e36

Please sign in to comment.