fix(upgrade): improve downgrading-related error messages (#26217)

Make the error messages thrown when instantiating downgraded components,
injectables and modules more descriptive and actionable, also taking
into account incorrect use of the `downgradedModule` field.

PR Close #26217
This commit is contained in:
George Kalpakas
2018-10-08 15:25:37 +03:00
committed by Kara Erickson
parent 93837e9545
commit 7dbc103cbe
12 changed files with 333 additions and 54 deletions

View File

@ -23,10 +23,12 @@ export const $TEMPLATE_REQUEST = '$templateRequest';
export const $$TESTABILITY = '$$testability';
export const COMPILER_KEY = '$$angularCompiler';
export const DOWNGRADED_MODULE_COUNT_KEY = '$$angularDowngradedModuleCount';
export const GROUP_PROJECTABLE_NODES_KEY = '$$angularGroupProjectableNodes';
export const INJECTOR_KEY = '$$angularInjector';
export const LAZY_MODULE_REF = '$$angularLazyModuleRef';
export const NG_ZONE_KEY = '$$angularNgZone';
export const UPGRADE_APP_TYPE_KEY = '$$angularUpgradeAppType';
export const REQUIRE_INJECTOR = '?^^' + INJECTOR_KEY;
export const REQUIRE_NG_MODEL = '?ngModel';

View File

@ -11,7 +11,7 @@ import {ComponentFactory, ComponentFactoryResolver, Injector, NgZone, Type} from
import * as angular from './angular1';
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, LAZY_MODULE_REF, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
import {LazyModuleRef, controllerKey, getComponentName, isFunction} from './util';
import {LazyModuleRef, controllerKey, getTypeName, isFunction, validateInjectionKey} from './util';
interface Thenable<T> {
@ -104,6 +104,10 @@ export function downgradeComponent(info: {
if (!parentInjector) {
const downgradedModule = info.downgradedModule || '';
const lazyModuleRefKey = `${LAZY_MODULE_REF}${downgradedModule}`;
const attemptedAction = `instantiating component '${getTypeName(info.component)}'`;
validateInjectionKey($injector, downgradedModule, lazyModuleRefKey, attemptedAction);
const lazyModuleRef = $injector.get(lazyModuleRefKey) as LazyModuleRef;
needsNgZone = lazyModuleRef.needsNgZone;
parentInjector = lazyModuleRef.injector || lazyModuleRef.promise as Promise<Injector>;
@ -116,7 +120,7 @@ export function downgradeComponent(info: {
componentFactoryResolver.resolveComponentFactory(info.component) !;
if (!componentFactory) {
throw new Error('Expecting ComponentFactory for: ' + getComponentName(info.component));
throw new Error(`Expecting ComponentFactory for: ${getTypeName(info.component)}`);
}
const injectorPromise = new ParentInjectorPromise(element);

View File

@ -11,7 +11,7 @@ import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, Event
import * as angular from './angular1';
import {PropertyBinding} from './component_info';
import {$SCOPE} from './constants';
import {getComponentName, hookupNgModel, strictEquals} from './util';
import {getTypeName, hookupNgModel, strictEquals} from './util';
const INITIAL_VALUE = {
__UNINITIALIZED__: true
@ -208,7 +208,7 @@ export class DowngradeComponentAdapter {
});
} else {
throw new Error(
`Missing emitter '${output.prop}' on component '${getComponentName(this.componentFactory.componentType)}'!`);
`Missing emitter '${output.prop}' on component '${getTypeName(this.componentFactory.componentType)}'!`);
}
}

View File

@ -7,7 +7,9 @@
*/
import {Injector} from '@angular/core';
import {INJECTOR_KEY} from './constants';
import * as angular from './angular1';
import {$INJECTOR, INJECTOR_KEY} from './constants';
import {getTypeName, isFunction, validateInjectionKey} from './util';
/**
* @description
@ -70,8 +72,17 @@ import {INJECTOR_KEY} from './constants';
* @publicApi
*/
export function downgradeInjectable(token: any, downgradedModule: string = ''): Function {
const factory = function(i: Injector) { return i.get(token); };
(factory as any)['$inject'] = [`${INJECTOR_KEY}${downgradedModule}`];
const factory = function($injector: angular.IInjectorService) {
const injectorKey = `${INJECTOR_KEY}${downgradedModule}`;
const injectableName = isFunction(token) ? getTypeName(token) : String(token);
const attemptedAction = `instantiating injectable '${injectableName}'`;
validateInjectionKey($injector, downgradedModule, injectorKey, attemptedAction);
const injector: Injector = $injector.get(injectorKey);
return injector.get(token);
};
(factory as any)['$inject'] = [$INJECTOR];
return factory;
}

View File

@ -8,6 +8,7 @@
import {Injector, Type} from '@angular/core';
import * as angular from './angular1';
import {DOWNGRADED_MODULE_COUNT_KEY, UPGRADE_APP_TYPE_KEY} from './constants';
const DIRECTIVE_PREFIX_REGEXP = /^(?:x|data)[:\-_]/i;
const DIRECTIVE_SPECIAL_CHARS_REGEXP = /[:\-_]+(.)/g;
@ -32,15 +33,66 @@ export function directiveNormalize(name: string): string {
.replace(DIRECTIVE_SPECIAL_CHARS_REGEXP, (_, letter) => letter.toUpperCase());
}
export function getComponentName(component: Type<any>): string {
// Return the name of the component or the first line of its stringified version.
return (component as any).overriddenName || component.name || component.toString().split('\n')[0];
export function getTypeName(type: Type<any>): string {
// Return the name of the type or the first line of its stringified version.
return (type as any).overriddenName || type.name || type.toString().split('\n')[0];
}
export function getDowngradedModuleCount($injector: angular.IInjectorService): number {
return $injector.has(DOWNGRADED_MODULE_COUNT_KEY) ? $injector.get(DOWNGRADED_MODULE_COUNT_KEY) :
0;
}
export function getUpgradeAppType($injector: angular.IInjectorService): UpgradeAppType {
return $injector.has(UPGRADE_APP_TYPE_KEY) ? $injector.get(UPGRADE_APP_TYPE_KEY) :
UpgradeAppType.None;
}
export function isFunction(value: any): value is Function {
return typeof value === 'function';
}
export function validateInjectionKey(
$injector: angular.IInjectorService, downgradedModule: string, injectionKey: string,
attemptedAction: string): void {
const upgradeAppType = getUpgradeAppType($injector);
const downgradedModuleCount = getDowngradedModuleCount($injector);
// Check for common errors.
switch (upgradeAppType) {
case UpgradeAppType.Dynamic:
case UpgradeAppType.Static:
if (downgradedModule) {
throw new Error(
`Error while ${attemptedAction}: 'downgradedModule' unexpectedly specified.\n` +
'You should not specify a value for \'downgradedModule\', unless you are downgrading ' +
'more than one Angular module (via \'downgradeModule()\').');
}
break;
case UpgradeAppType.Lite:
if (!downgradedModule && (downgradedModuleCount >= 2)) {
throw new Error(
`Error while ${attemptedAction}: 'downgradedModule' not specified.\n` +
'This application contains more than one downgraded Angular module, thus you need to ' +
'always specify \'downgradedModule\' when downgrading components and injectables.');
}
if (!$injector.has(injectionKey)) {
throw new Error(
`Error while ${attemptedAction}: Unable to find the specified downgraded module.\n` +
'Did you forget to downgrade an Angular module or include it in the AngularJS ' +
'application?');
}
break;
default:
throw new Error(
`Error while ${attemptedAction}: Not a valid '@angular/upgrade' application.\n` +
'Did you forget to downgrade an Angular module or include it in the AngularJS ' +
'application?');
}
}
export class Deferred<R> {
promise: Promise<R>;
// TODO(issue/24571): remove '!'.
@ -64,6 +116,20 @@ export interface LazyModuleRef {
promise?: Promise<Injector>;
}
export const enum UpgradeAppType {
// App NOT using `@angular/upgrade`. (This should never happen in an `ngUpgrade` app.)
None,
// App using the deprecated `@angular/upgrade` APIs (a.k.a. dynamic `ngUpgrade`).
Dynamic,
// App using `@angular/upgrade/static` with `UpgradeModule`.
Static,
// App using @angular/upgrade/static` with `downgradeModule()` (a.k.a `ngUpgrade`-lite ).
Lite,
}
/**
* @return Whether the passed-in component implements the subset of the
* `ControlValueAccessor` interface needed for AngularJS `ng-model`

View File

@ -10,10 +10,10 @@ import {Compiler, CompilerOptions, Directive, Injector, NgModule, NgModuleRef, N
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '../common/angular1';
import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, LAZY_MODULE_REF, NG_ZONE_KEY} from '../common/constants';
import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, LAZY_MODULE_REF, NG_ZONE_KEY, UPGRADE_APP_TYPE_KEY} from '../common/constants';
import {downgradeComponent} from '../common/downgrade_component';
import {downgradeInjectable} from '../common/downgrade_injectable';
import {Deferred, LazyModuleRef, controllerKey, onError} from '../common/util';
import {Deferred, LazyModuleRef, UpgradeAppType, controllerKey, onError} from '../common/util';
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
@ -506,7 +506,8 @@ export class UpgradeAdapter {
this.ngZone = new NgZone({enableLongStackTrace: Zone.hasOwnProperty('longStackTraceZoneSpec')});
this.ng2BootstrapDeferred = new Deferred();
ng1Module.factory(INJECTOR_KEY, () => this.moduleRef !.injector.get(Injector))
ng1Module.constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Dynamic)
.factory(INJECTOR_KEY, () => this.moduleRef !.injector.get(Injector))
.factory(
LAZY_MODULE_REF,
[

View File

@ -10,8 +10,8 @@ import {Injector, NgModuleFactory, NgModuleRef, StaticProvider} from '@angular/c
import {platformBrowser} from '@angular/platform-browser';
import * as angular from '../common/angular1';
import {$INJECTOR, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_MODULE_NAME} from '../common/constants';
import {LazyModuleRef, isFunction} from '../common/util';
import {$INJECTOR, $PROVIDE, DOWNGRADED_MODULE_COUNT_KEY, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_APP_TYPE_KEY, UPGRADE_MODULE_NAME} from '../common/constants';
import {LazyModuleRef, UpgradeAppType, getDowngradedModuleCount, isFunction} from '../common/util';
import {angular1Providers, setTempInjectorRef} from './angular1_providers';
import {NgAdapterInjector} from './util';
@ -119,31 +119,41 @@ export function downgradeModule<T>(
// Create an ng1 module to bootstrap.
angular.module(lazyModuleName, [])
.constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Lite)
.factory(INJECTOR_KEY, [lazyInjectorKey, identity])
.factory(
lazyInjectorKey,
() => {
if (!injector) {
throw new Error(
'Trying to get the Angular injector before bootstrapping an Angular module.');
'Trying to get the Angular injector before bootstrapping the corresponding ' +
'Angular module.');
}
return injector;
})
.factory(LAZY_MODULE_REF, [lazyModuleRefKey, identity])
.factory(lazyModuleRefKey, [
$INJECTOR,
($injector: angular.IInjectorService) => {
setTempInjectorRef($injector);
const result: LazyModuleRef = {
needsNgZone: true,
promise: bootstrapFn(angular1Providers).then(ref => {
injector = result.injector = new NgAdapterInjector(ref.injector);
injector.get($INJECTOR);
.factory(
lazyModuleRefKey,
[
$INJECTOR,
($injector: angular.IInjectorService) => {
setTempInjectorRef($injector);
const result: LazyModuleRef = {
needsNgZone: true,
promise: bootstrapFn(angular1Providers).then(ref => {
injector = result.injector = new NgAdapterInjector(ref.injector);
injector.get($INJECTOR);
return injector;
})
};
return result;
return injector;
})
};
return result;
}
])
.config([
$INJECTOR, $PROVIDE,
($injector: angular.IInjectorService, $provide: angular.IProvideService) => {
$provide.constant(DOWNGRADED_MODULE_COUNT_KEY, getDowngradedModuleCount($injector) + 1);
}
]);

View File

@ -9,8 +9,8 @@
import {Injector, NgModule, NgZone, Testability} from '@angular/core';
import * as angular from '../common/angular1';
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $INTERVAL, $PROVIDE, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_MODULE_NAME} from '../common/constants';
import {LazyModuleRef, controllerKey} from '../common/util';
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $INTERVAL, $PROVIDE, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_APP_TYPE_KEY, UPGRADE_MODULE_NAME} from '../common/constants';
import {LazyModuleRef, UpgradeAppType, controllerKey} from '../common/util';
import {angular1Providers, setTempInjectorRef} from './angular1_providers';
import {NgAdapterInjector} from './util';
@ -173,6 +173,8 @@ export class UpgradeModule {
angular
.module(INIT_MODULE_NAME, [])
.constant(UPGRADE_APP_TYPE_KEY, UpgradeAppType.Static)
.value(INJECTOR_KEY, this.injector)
.factory(