fix(upgrade): allow nesting components from different downgraded modules (#27217)
PR Close #27217
This commit is contained in:

committed by
Matias Niemelä

parent
326b464d20
commit
bc0ee01d09
@ -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, UpgradeAppType, controllerKey, getTypeName, getUpgradeAppType, isFunction, validateInjectionKey} from './util';
|
||||
import {LazyModuleRef, UpgradeAppType, controllerKey, getDowngradedModuleCount, getTypeName, getUpgradeAppType, isFunction, validateInjectionKey} from './util';
|
||||
|
||||
|
||||
interface Thenable<T> {
|
||||
@ -91,6 +91,10 @@ export function downgradeComponent(info: {
|
||||
!isNgUpgradeLite ? cb => cb : cb => () => NgZone.isInAngularZone() ? cb() : ngZone.run(cb);
|
||||
let ngZone: NgZone;
|
||||
|
||||
// When downgrading multiple modules, special handling is needed wrt injectors.
|
||||
const hasMultipleDowngradedModules =
|
||||
isNgUpgradeLite && (getDowngradedModuleCount($injector) > 1);
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
terminal: true,
|
||||
@ -102,10 +106,11 @@ export function downgradeComponent(info: {
|
||||
// been compiled.
|
||||
|
||||
const ngModel: angular.INgModelController = required[1];
|
||||
let parentInjector: Injector|Thenable<Injector>|undefined = required[0];
|
||||
const parentInjector: Injector|Thenable<Injector>|undefined = required[0];
|
||||
let moduleInjector: Injector|Thenable<Injector>|undefined = undefined;
|
||||
let ranAsync = false;
|
||||
|
||||
if (!parentInjector) {
|
||||
if (!parentInjector || hasMultipleDowngradedModules) {
|
||||
const downgradedModule = info.downgradedModule || '';
|
||||
const lazyModuleRefKey = `${LAZY_MODULE_REF}${downgradedModule}`;
|
||||
const attemptedAction = `instantiating component '${getTypeName(info.component)}'`;
|
||||
@ -113,12 +118,55 @@ export function downgradeComponent(info: {
|
||||
validateInjectionKey($injector, downgradedModule, lazyModuleRefKey, attemptedAction);
|
||||
|
||||
const lazyModuleRef = $injector.get(lazyModuleRefKey) as LazyModuleRef;
|
||||
parentInjector = lazyModuleRef.injector || lazyModuleRef.promise as Promise<Injector>;
|
||||
moduleInjector = lazyModuleRef.injector || lazyModuleRef.promise as Promise<Injector>;
|
||||
}
|
||||
|
||||
const doDowngrade = (injector: Injector) => {
|
||||
// Notes:
|
||||
//
|
||||
// There are two injectors: `finalModuleInjector` and `finalParentInjector` (they might be
|
||||
// the same instance, but that is irrelevant):
|
||||
// - `finalModuleInjector` is used to retrieve `ComponentFactoryResolver`, thus it must be
|
||||
// on the same tree as the `NgModule` that declares this downgraded component.
|
||||
// - `finalParentInjector` is used for all other injection purposes.
|
||||
// (Note that Angular knows to only traverse the component-tree part of that injector,
|
||||
// when looking for an injectable and then switch to the module injector.)
|
||||
//
|
||||
// There are basically three cases:
|
||||
// - If there is no parent component (thus no `parentInjector`), we bootstrap the downgraded
|
||||
// `NgModule` and use its injector as both `finalModuleInjector` and
|
||||
// `finalParentInjector`.
|
||||
// - If there is a parent component (and thus a `parentInjector`) and we are sure that it
|
||||
// belongs to the same `NgModule` as this downgraded component (e.g. because there is only
|
||||
// one downgraded module, we use that `parentInjector` as both `finalModuleInjector` and
|
||||
// `finalParentInjector`.
|
||||
// - If there is a parent component, but it may belong to a different `NgModule`, then we
|
||||
// use the `parentInjector` as `finalParentInjector` and this downgraded component's
|
||||
// declaring `NgModule`'s injector as `finalModuleInjector`.
|
||||
// Note 1: If the `NgModule` is already bootstrapped, we just get its injector (we don't
|
||||
// bootstrap again).
|
||||
// Note 2: It is possible that (while there are multiple downgraded modules) this
|
||||
// downgraded component and its parent component both belong to the same NgModule.
|
||||
// In that case, we could have used the `parentInjector` as both
|
||||
// `finalModuleInjector` and `finalParentInjector`, but (for simplicity) we are
|
||||
// treating this case as if they belong to different `NgModule`s. That doesn't
|
||||
// really affect anything, since `parentInjector` has `moduleInjector` as ancestor
|
||||
// and trying to resolve `ComponentFactoryResolver` from either one will return
|
||||
// the same instance.
|
||||
|
||||
// If there is a parent component, use its injector as parent injector.
|
||||
// If this is a "top-level" Angular component, use the module injector.
|
||||
const finalParentInjector = parentInjector || moduleInjector !;
|
||||
|
||||
// If this is a "top-level" Angular component or the parent component may belong to a
|
||||
// different `NgModule`, use the module injector for module-specific dependencies.
|
||||
// If there is a parent component that belongs to the same `NgModule`, use its injector.
|
||||
const finalModuleInjector = moduleInjector || parentInjector !;
|
||||
|
||||
const doDowngrade = (injector: Injector, moduleInjector: Injector) => {
|
||||
// Retrieve `ComponentFactoryResolver` from the injector tied to the `NgModule` this
|
||||
// component belongs to.
|
||||
const componentFactoryResolver: ComponentFactoryResolver =
|
||||
injector.get(ComponentFactoryResolver);
|
||||
moduleInjector.get(ComponentFactoryResolver);
|
||||
const componentFactory: ComponentFactory<any> =
|
||||
componentFactoryResolver.resolveComponentFactory(info.component) !;
|
||||
|
||||
@ -146,18 +194,20 @@ export function downgradeComponent(info: {
|
||||
}
|
||||
};
|
||||
|
||||
const downgradeFn = !isNgUpgradeLite ? doDowngrade : (injector: Injector) => {
|
||||
if (!ngZone) {
|
||||
ngZone = injector.get(NgZone);
|
||||
}
|
||||
const downgradeFn =
|
||||
!isNgUpgradeLite ? doDowngrade : (pInjector: Injector, mInjector: Injector) => {
|
||||
if (!ngZone) {
|
||||
ngZone = pInjector.get(NgZone);
|
||||
}
|
||||
|
||||
wrapCallback(() => doDowngrade(injector))();
|
||||
};
|
||||
wrapCallback(() => doDowngrade(pInjector, mInjector))();
|
||||
};
|
||||
|
||||
if (isThenable<Injector>(parentInjector)) {
|
||||
parentInjector.then(downgradeFn);
|
||||
if (isThenable(finalParentInjector) || isThenable(finalModuleInjector)) {
|
||||
Promise.all([finalParentInjector, finalModuleInjector])
|
||||
.then(([pInjector, mInjector]) => downgradeFn(pInjector, mInjector));
|
||||
} else {
|
||||
downgradeFn(parentInjector);
|
||||
downgradeFn(finalParentInjector, finalModuleInjector);
|
||||
}
|
||||
|
||||
ranAsync = true;
|
||||
|
Reference in New Issue
Block a user