feat(core): add source to `StaticInjectorError` message (#20817)
Closes #19302 PR Close #20817
This commit is contained in:
parent
634d33f5dd
commit
b7738e1fe5
|
@ -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 = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
|
||||
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
|
||||
if (!exceptionHandler) {
|
||||
|
|
|
@ -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<any, Record>;
|
||||
|
||||
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<any, Record>();
|
||||
records.set(
|
||||
Injector, <Record>{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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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!');
|
||||
});
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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<any> {
|
||||
function bootstrap(
|
||||
cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [],
|
||||
imports: Type<any>[] = []): Promise<any> {
|
||||
@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: '<cmp></cmp>',
|
||||
})
|
||||
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) => {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue