diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index 95f7ebbe97..87e8bfb1ff 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -102,7 +102,8 @@ export function createPlatformFactory( parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null, name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) => PlatformRef { - const marker = new InjectionToken(`Platform: ${name}`); + const desc = `Platform: ${name}`; + const marker = new InjectionToken(desc); return (extraProviders: StaticProvider[] = []) => { let platform = getPlatform(); if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { @@ -110,8 +111,9 @@ export function createPlatformFactory( parentPlatformFactory( providers.concat(extraProviders).concat({provide: marker, useValue: true})); } else { - createPlatform(Injector.create( - providers.concat(extraProviders).concat({provide: marker, useValue: true}))); + const injectedProviders: StaticProvider[] = + providers.concat(extraProviders).concat({provide: marker, useValue: true}); + createPlatform(Injector.create({providers: injectedProviders, name: desc})); } } return assertPlatform(marker); @@ -224,10 +226,12 @@ export class PlatformRef { // pass that as parent to the NgModuleFactory. const ngZoneOption = options ? options.ngZone : undefined; const ngZone = getNgZone(ngZoneOption); + const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; // Attention: Don't use ApplicationRef.run here, // as we want to be sure that all possible constructor calls are inside `ngZone.run`! return ngZone.run(() => { - const ngZoneInjector = Injector.create([{provide: NgZone, useValue: ngZone}], this.injector); + const ngZoneInjector = Injector.create( + {providers: providers, parent: this.injector, name: moduleFactory.moduleType.name}); const moduleRef = >moduleFactory.create(ngZoneInjector); const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null); if (!exceptionHandler) { diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index a0a380a414..e6df410642 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -8,12 +8,12 @@ import {Type} from '../type'; import {stringify} from '../util'; - import {resolveForwardRef} from './forward_ref'; import {InjectionToken} from './injection_token'; import {Inject, Optional, Self, SkipSelf} from './metadata'; import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider'; +export const SOURCE = '__source'; const _THROW_IF_NOT_FOUND = new Object(); export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; @@ -64,6 +64,13 @@ export abstract class Injector { */ abstract get(token: any, notFoundValue?: any): any; + /** + * @deprecated from v5 use the new signature Injector.create(options) + */ + static create(providers: StaticProvider[], parent?: Injector): Injector; + + static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector; + /** * Create a new Injector which is configure using `StaticProvider`s. * @@ -71,8 +78,14 @@ export abstract class Injector { * * {@example core/di/ts/provider_spec.ts region='ConstructorProvider'} */ - static create(providers: StaticProvider[], parent?: Injector): Injector { - return new StaticInjector(providers, parent); + static create( + options: StaticProvider[]|{providers: StaticProvider[], parent?: Injector, name?: string}, + parent?: Injector): Injector { + if (Array.isArray(options)) { + return new StaticInjector(options, parent); + } else { + return new StaticInjector(options.providers, options.parent, options.name || null); + } } } @@ -103,11 +116,14 @@ const NO_NEW_LINE = 'ɵ'; export class StaticInjector implements Injector { readonly parent: Injector; + readonly source: string|null; private _records: Map; - constructor(providers: StaticProvider[], parent: Injector = NULL_INJECTOR) { + constructor( + providers: StaticProvider[], parent: Injector = NULL_INJECTOR, source: string|null = null) { this.parent = parent; + this.source = source; const records = this._records = new Map(); records.set( Injector, {token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false}); @@ -122,7 +138,10 @@ export class StaticInjector implements Injector { return tryResolveToken(token, record, this._records, this.parent, notFoundValue); } catch (e) { const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH]; - e.message = formatError('\n' + e.message, tokenPath); + if (token[SOURCE]) { + tokenPath.unshift(token[SOURCE]); + } + e.message = formatError('\n' + e.message, tokenPath, this.source); e[NG_TOKEN_PATH] = tokenPath; e[NG_TEMP_TOKEN_PATH] = null; throw e; @@ -336,7 +355,7 @@ function computeDeps(provider: StaticProvider): DependencyRecord[] { return deps; } -function formatError(text: string, obj: any): string { +function formatError(text: string, obj: any, source: string | null = null): string { text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text; let context = stringify(obj); if (obj instanceof Array) { @@ -352,7 +371,7 @@ function formatError(text: string, obj: any): string { } context = `{${parts.join(', ')}}`; } - return `StaticInjectorError[${context}]: ${text.replace(NEW_LINE, '\n ')}`; + return `StaticInjectorError${source ? '(' + source + ')' : ''}[${context}]: ${text.replace(NEW_LINE, '\n ')}`; } function staticError(text: string, obj: any): Error { diff --git a/packages/core/src/view/ng_module.ts b/packages/core/src/view/ng_module.ts index 5c57bf278e..74fc91ddb2 100644 --- a/packages/core/src/view/ng_module.ts +++ b/packages/core/src/view/ng_module.ts @@ -9,6 +9,7 @@ import {resolveForwardRef} from '../di/forward_ref'; import {Injector} from '../di/injector'; import {NgModuleRef} from '../linker/ng_module_factory'; +import {stringify} from '../util'; import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types'; import {splitDepsDsl, tokenKey} from './util'; @@ -25,7 +26,7 @@ export function moduleProvideDef( // lowered the expression and then stopped evaluating it, // i.e. also didn't unwrap it. value = resolveForwardRef(value); - const depDefs = splitDepsDsl(deps); + const depDefs = splitDepsDsl(deps, stringify(token)); return { // will bet set by the module definition index: -1, diff --git a/packages/core/src/view/provider.ts b/packages/core/src/view/provider.ts index 1576b566b5..65371c9d19 100644 --- a/packages/core/src/view/provider.ts +++ b/packages/core/src/view/provider.ts @@ -12,7 +12,7 @@ import {ElementRef} from '../linker/element_ref'; import {TemplateRef} from '../linker/template_ref'; import {ViewContainerRef} from '../linker/view_container_ref'; import {Renderer as RendererV1, Renderer2} from '../render/api'; - +import {stringify} from '../util'; import {createChangeDetectorRef, createInjector, createRendererV1} from './refs'; import {BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, ViewData, ViewFlags, ViewState, asElementData, asProviderData, shouldCallLifecycleInitHook} from './types'; import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitDepsDsl, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util'; @@ -83,7 +83,7 @@ export function _def( // i.e. also didn't unwrap it. value = resolveForwardRef(value); - const depDefs = splitDepsDsl(deps); + const depDefs = splitDepsDsl(deps, stringify(token)); return { // will bet set by the view definition diff --git a/packages/core/src/view/util.ts b/packages/core/src/view/util.ts index a1b3219a9e..832f05cdbe 100644 --- a/packages/core/src/view/util.ts +++ b/packages/core/src/view/util.ts @@ -7,10 +7,10 @@ */ import {WrappedValue, devModeEqual} from '../change_detection/change_detection'; +import {SOURCE} from '../di/injector'; import {ViewEncapsulation} from '../metadata/view'; import {RendererType2} from '../render/api'; import {looseIdentical, stringify} from '../util'; - import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {BindingDef, BindingFlags, Definition, DefinitionFactory, DepDef, DepFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types'; @@ -209,7 +209,7 @@ export function splitMatchedQueriesDsl( return {matchedQueries, references, matchedQueryIds}; } -export function splitDepsDsl(deps: ([DepFlags, any] | any)[]): DepDef[] { +export function splitDepsDsl(deps: ([DepFlags, any] | any)[], sourceName?: string): DepDef[] { return deps.map(value => { let token: any; let flags: DepFlags; @@ -219,6 +219,9 @@ export function splitDepsDsl(deps: ([DepFlags, any] | any)[]): DepDef[] { flags = DepFlags.None; token = value; } + if (token && (typeof token === 'function' || typeof token === 'object') && sourceName) { + Object.defineProperty(token, SOURCE, {value: sourceName, configurable: true}); + } return {flags, token, tokenKey: tokenKey(token)}; }); } diff --git a/packages/core/test/view/provider_spec.ts b/packages/core/test/view/provider_spec.ts index f1941e687e..fed49c5ca8 100644 --- a/packages/core/test/view/provider_spec.ts +++ b/packages/core/test/view/provider_spec.ts @@ -147,8 +147,8 @@ export function main() { expect(() => createAndGetRootNodes(compViewDef(rootElNodes))) .toThrowError( - 'StaticInjectorError[Dep]: \n' + - ' StaticInjectorError[Dep]: \n' + + 'StaticInjectorError(DynamicTestModule)[SomeService -> Dep]: \n' + + ' StaticInjectorError(Platform: core)[SomeService -> Dep]: \n' + ' NullInjectorError: No provider for Dep!'); const nonRootElNodes = [ @@ -161,8 +161,8 @@ export function main() { expect(() => createAndGetRootNodes(compViewDef(nonRootElNodes))) .toThrowError( - 'StaticInjectorError[Dep]: \n' + - ' StaticInjectorError[Dep]: \n' + + 'StaticInjectorError(DynamicTestModule)[SomeService -> Dep]: \n' + + ' StaticInjectorError(Platform: core)[SomeService -> Dep]: \n' + ' NullInjectorError: No provider for Dep!'); }); @@ -186,8 +186,8 @@ export function main() { directiveDef(1, NodeFlags.None, null, 0, SomeService, ['nonExistingDep']) ]))) .toThrowError( - 'StaticInjectorError[nonExistingDep]: \n' + - ' StaticInjectorError[nonExistingDep]: \n' + + 'StaticInjectorError(DynamicTestModule)[nonExistingDep]: \n' + + ' StaticInjectorError(Platform: core)[nonExistingDep]: \n' + ' NullInjectorError: No provider for nonExistingDep!'); }); diff --git a/packages/core/testing/src/test_bed.ts b/packages/core/testing/src/test_bed.ts index f71f4f6648..66749dfaf7 100644 --- a/packages/core/testing/src/test_bed.ts +++ b/packages/core/testing/src/test_bed.ts @@ -355,8 +355,12 @@ export class TestBed implements Injector { } const ngZone = new NgZone({enableLongStackTrace: true}); - const ngZoneInjector = - Injector.create([{provide: NgZone, useValue: ngZone}], this.platform.injector); + const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}]; + const ngZoneInjector = Injector.create({ + providers: providers, + parent: this.platform.injector, + name: this._moduleFactory.moduleType.name + }); this._moduleRef = this._moduleFactory.create(ngZoneInjector); // ApplicationInitStatus.runInitializers() is marked @internal to core. So casting to any // before accessing it. diff --git a/packages/examples/core/di/ts/provider_spec.ts b/packages/examples/core/di/ts/provider_spec.ts index 4a273df1dc..e50e36428e 100644 --- a/packages/examples/core/di/ts/provider_spec.ts +++ b/packages/examples/core/di/ts/provider_spec.ts @@ -135,7 +135,7 @@ export function main() { name = 'square'; } - const injector = Injector.create([{provide: Square, deps: []}]); + const injector = Injector.create({providers: [{provide: Square, deps: []}]}); const shape: Square = injector.get(Square); expect(shape.name).toEqual('square'); diff --git a/packages/platform-browser/test/browser/bootstrap_spec.ts b/packages/platform-browser/test/browser/bootstrap_spec.ts index f1b0cf2926..e028cbbc1f 100644 --- a/packages/platform-browser/test/browser/bootstrap_spec.ts +++ b/packages/platform-browser/test/browser/bootstrap_spec.ts @@ -7,12 +7,12 @@ */ import {isPlatformBrowser} from '@angular/common'; -import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, StaticProvider, VERSION, createPlatformFactory, ɵstringify as stringify} from '@angular/core'; +import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, StaticProvider, Type, VERSION, createPlatformFactory} from '@angular/core'; import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref'; import {Console} from '@angular/core/src/console'; import {ComponentRef} from '@angular/core/src/linker/component_factory'; import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability'; -import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it} from '@angular/core/testing/src/testing_internal'; +import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, iit, inject, it} from '@angular/core/testing/src/testing_internal'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -112,10 +112,11 @@ class DummyConsole implements Console { class TestModule {} -function bootstrap(cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [ -]): Promise { +function bootstrap( + cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [], + imports: Type[] = []): Promise { @NgModule({ - imports: [BrowserModule], + imports: [BrowserModule, ...imports], declarations: [cmpType], bootstrap: [cmpType], providers: providers, @@ -183,6 +184,40 @@ export function main() { }); })); + it('should throw if no provider', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { + const logger = new MockConsole(); + const errorHandler = new ErrorHandler(); + errorHandler._console = logger as any; + + class IDontExist {} + + @Component({selector: 'cmp', template: 'Cmp'}) + class CustomCmp { + constructor(iDontExist: IDontExist) {} + } + + @Component({ + selector: 'hello-app', + template: '', + }) + class RootCmp { + } + + @NgModule({declarations: [CustomCmp], exports: [CustomCmp]}) + class CustomModule { + } + + bootstrap(RootCmp, [{provide: ErrorHandler, useValue: errorHandler}], [], [ + CustomModule + ]).then(null, (e: Error) => { + expect(e.message).toContain(`StaticInjectorError(TestModule)[CustomCmp -> IDontExist]: + StaticInjectorError(Platform: core)[CustomCmp -> IDontExist]: + NullInjectorError: No provider for IDontExist!`); + async.done(); + return null; + }); + })); + if (getDOM().supportsDOMEvents()) { it('should forward the error to promise when bootstrap fails', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts index 74509791f5..4e44bfcf9f 100644 --- a/packages/upgrade/src/common/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/downgrade_component_adapter.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, Testability, TestabilityRegistry, Type} from '@angular/core'; +import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, StaticProvider, Testability, TestabilityRegistry, Type} from '@angular/core'; import * as angular from './angular1'; import {PropertyBinding} from './component_info'; @@ -54,8 +54,9 @@ export class DowngradeComponentAdapter { } createComponent(projectableNodes: Node[][]) { - const childInjector = - Injector.create([{provide: $SCOPE, useValue: this.componentScope}], this.parentInjector); + const providers: StaticProvider[] = [{provide: $SCOPE, useValue: this.componentScope}]; + const childInjector = Injector.create( + {providers: providers, parent: this.parentInjector, name: 'DowngradeComponentAdapter'}); this.componentRef = this.componentFactory.create(childInjector, projectableNodes, this.element[0]); diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 51fe21bb95..b4edf322f4 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -476,7 +476,12 @@ export declare abstract class Injector { /** @deprecated */ abstract get(token: any, notFoundValue?: any): any; static NULL: Injector; static THROW_IF_NOT_FOUND: Object; - static create(providers: StaticProvider[], parent?: Injector): Injector; + /** @deprecated */ static create(providers: StaticProvider[], parent?: Injector): Injector; + static create(options: { + providers: StaticProvider[]; + parent?: Injector; + name?: string; + }): Injector; } /** @stable */