fix(ivy): ngUpgrade should distinguish element and module injectors (#28313)

There are cases where we should check an element injector but don't go
into the associated module injector if a token is not found. In both the
view engine and ngIvy this is acheived by passing the
`NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR` as the `notFoundValue`.

Before this fix the view engine and ngIvy were using different objects to
represent `NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR`. This was causing problems
as ngUpgrade is using `NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR` const in its
`NgAdapterInjector` to prevent searching of module injectors.

This commit makes sure that ngIvy is using the same object to represent
`NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR` as the view engine.

PR Close #28313
This commit is contained in:
Pawel Kozlowski 2019-01-23 13:26:48 +01:00 committed by Jason Aden
parent 22a43cff4d
commit 32c61f434c
3 changed files with 140 additions and 145 deletions

View File

@ -19,6 +19,7 @@ import {RendererFactory2} from '../render/api';
import {Sanitizer} from '../sanitization/security';
import {assertDefined} from '../util/assert';
import {VERSION} from '../version';
import {NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '../view/provider';
import {assertComponentType} from './assert';
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
@ -74,8 +75,6 @@ export const SCHEDULER = new InjectionToken<((fn: () => void) => void)>('SCHEDUL
factory: () => defaultScheduler,
});
const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
function createChainedInjector(rootViewInjector: Injector, moduleInjector: Injector): Injector {
return {
get: <T>(token: Type<T>| InjectionToken<T>, notFoundValue?: T): T => {

View File

@ -736,56 +736,54 @@ withEachNg1Version(() => {
});
}));
fixmeIvy(
'FW-717: Injector on lazy loaded components are not the same as their NgModule\'s injector')
.it('should work with ng2 lazy loaded components', async(() => {
let componentInjector: Injector;
it('should work with ng2 lazy loaded components', async(() => {
let componentInjector: Injector;
@Component({selector: 'ng2', template: ''})
class Ng2Component {
constructor(injector: Injector) { componentInjector = injector; }
}
@Component({selector: 'ng2', template: ''})
class Ng2Component {
constructor(injector: Injector) { componentInjector = injector; }
}
@NgModule({
declarations: [Ng2Component],
entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule],
})
class Ng2Module {
ngDoBootstrap() {}
}
@NgModule({
declarations: [Ng2Component],
entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule],
})
class Ng2Module {
ngDoBootstrap() {}
}
@Component({template: ''})
class LazyLoadedComponent {
constructor(public module: NgModuleRef<any>) {}
}
@Component({template: ''})
class LazyLoadedComponent {
constructor(public module: NgModuleRef<any>) {}
}
@NgModule({
declarations: [LazyLoadedComponent],
entryComponents: [LazyLoadedComponent],
})
class LazyLoadedModule {
}
@NgModule({
declarations: [LazyLoadedComponent],
entryComponents: [LazyLoadedComponent],
})
class LazyLoadedModule {
}
const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component}));
const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component}));
const element = html('<ng2></ng2>');
const element = html('<ng2></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
const modInjector = upgrade.injector;
// Emulate the router lazy loading a module and creating a component
const compiler = modInjector.get(Compiler);
const modFactory = compiler.compileModuleSync(LazyLoadedModule);
const childMod = modFactory.create(modInjector);
const cmpFactory = childMod.componentFactoryResolver.resolveComponentFactory(
LazyLoadedComponent) !;
const lazyCmp = cmpFactory.create(componentInjector);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
const modInjector = upgrade.injector;
// Emulate the router lazy loading a module and creating a component
const compiler = modInjector.get(Compiler);
const modFactory = compiler.compileModuleSync(LazyLoadedModule);
const childMod = modFactory.create(modInjector);
const cmpFactory =
childMod.componentFactoryResolver.resolveComponentFactory(LazyLoadedComponent) !;
const lazyCmp = cmpFactory.create(componentInjector);
expect(lazyCmp.instance.module.injector === childMod.injector).toBe(true);
});
expect(lazyCmp.instance.module.injector === childMod.injector).toBe(true);
});
}));
}));
it('should throw if `downgradedModule` is specified', async(() => {
@Component({selector: 'ng2', template: ''})

View File

@ -416,127 +416,125 @@ withEachNg1Version(() => {
});
}));
fixmeIvy('FW-873: projected component injector hierarchy not wired up correctly')
.it('should correctly traverse the injector tree of downgraded components (from different modules)',
async(() => {
@Component({
selector: 'ng2A',
template: 'ng2A(<ng-content></ng-content>)',
providers: [
{provide: 'FOO', useValue: 'CompA-foo'},
{provide: 'BAR', useValue: 'CompA-bar'},
],
})
class Ng2ComponentA {
}
it('should correctly traverse the injector tree of downgraded components (from different modules)',
async(() => {
@Component({
selector: 'ng2A',
template: 'ng2A(<ng-content></ng-content>)',
providers: [
{provide: 'FOO', useValue: 'CompA-foo'},
{provide: 'BAR', useValue: 'CompA-bar'},
],
})
class Ng2ComponentA {
}
@Component({
selector: 'ng2B',
template: `
@Component({
selector: 'ng2B',
template: `
FOO:{{ foo }}
BAR:{{ bar }}
BAZ:{{ baz }}
QUX:{{ qux }}
QUUX:{{ quux }}
`,
providers: [
{provide: 'FOO', useValue: 'CompB-foo'},
],
})
class Ng2ComponentB {
constructor(
@Inject('FOO') public foo: string, @Inject('BAR') public bar: string,
@Inject('BAZ') public baz: string, @Inject('QUX') public qux: string,
@Inject('QUUX') public quux: string) {}
}
providers: [
{provide: 'FOO', useValue: 'CompB-foo'},
],
})
class Ng2ComponentB {
constructor(
@Inject('FOO') public foo: string, @Inject('BAR') public bar: string,
@Inject('BAZ') public baz: string, @Inject('QUX') public qux: string,
@Inject('QUUX') public quux: string) {}
}
@NgModule({
declarations: [Ng2ComponentA],
entryComponents: [Ng2ComponentA],
imports: [BrowserModule],
providers: [
{provide: 'FOO', useValue: 'ModA-foo'},
{provide: 'BAR', useValue: 'ModA-bar'},
{provide: 'BAZ', useValue: 'ModA-baz'},
{provide: 'QUX', useValue: 'ModA-qux'},
],
})
class Ng2ModuleA {
ngDoBootstrap() {}
}
@NgModule({
declarations: [Ng2ComponentA],
entryComponents: [Ng2ComponentA],
imports: [BrowserModule],
providers: [
{provide: 'FOO', useValue: 'ModA-foo'},
{provide: 'BAR', useValue: 'ModA-bar'},
{provide: 'BAZ', useValue: 'ModA-baz'},
{provide: 'QUX', useValue: 'ModA-qux'},
],
})
class Ng2ModuleA {
ngDoBootstrap() {}
}
@NgModule({
declarations: [Ng2ComponentB],
entryComponents: [Ng2ComponentB],
imports: [BrowserModule],
providers: [
{provide: 'FOO', useValue: 'ModB-foo'},
{provide: 'BAR', useValue: 'ModB-bar'},
{provide: 'BAZ', useValue: 'ModB-baz'},
],
})
class Ng2ModuleB {
ngDoBootstrap() {}
}
@NgModule({
declarations: [Ng2ComponentB],
entryComponents: [Ng2ComponentB],
imports: [BrowserModule],
providers: [
{provide: 'FOO', useValue: 'ModB-foo'},
{provide: 'BAR', useValue: 'ModB-bar'},
{provide: 'BAZ', useValue: 'ModB-baz'},
],
})
class Ng2ModuleB {
ngDoBootstrap() {}
}
const doDowngradeModule = (module: Type<any>) => {
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = getPlatform() || platformBrowserDynamic([
...extraProviders,
{provide: 'FOO', useValue: 'Plat-foo'},
{provide: 'BAR', useValue: 'Plat-bar'},
{provide: 'BAZ', useValue: 'Plat-baz'},
{provide: 'QUX', useValue: 'Plat-qux'},
{provide: 'QUUX', useValue: 'Plat-quux'},
]);
return platformRef.bootstrapModule(module);
};
return downgradeModule(bootstrapFn);
};
const doDowngradeModule = (module: Type<any>) => {
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = getPlatform() || platformBrowserDynamic([
...extraProviders,
{provide: 'FOO', useValue: 'Plat-foo'},
{provide: 'BAR', useValue: 'Plat-bar'},
{provide: 'BAZ', useValue: 'Plat-baz'},
{provide: 'QUX', useValue: 'Plat-qux'},
{provide: 'QUUX', useValue: 'Plat-quux'},
]);
return platformRef.bootstrapModule(module);
};
return downgradeModule(bootstrapFn);
};
const downModA = doDowngradeModule(Ng2ModuleA);
const downModB = doDowngradeModule(Ng2ModuleB);
const ng1Module = angular.module('ng1', [downModA, downModB])
.directive('ng2A', downgradeComponent({
component: Ng2ComponentA,
downgradedModule: downModA, propagateDigest,
}))
.directive('ng2B', downgradeComponent({
component: Ng2ComponentB,
downgradedModule: downModB, propagateDigest,
}));
const downModA = doDowngradeModule(Ng2ModuleA);
const downModB = doDowngradeModule(Ng2ModuleB);
const ng1Module = angular.module('ng1', [downModA, downModB])
.directive('ng2A', downgradeComponent({
component: Ng2ComponentA,
downgradedModule: downModA, propagateDigest,
}))
.directive('ng2B', downgradeComponent({
component: Ng2ComponentB,
downgradedModule: downModB, propagateDigest,
}));
const element = html(`
const element = html(`
<ng2-a><ng2-b ng-if="showB1"></ng2-b></ng2-a>
<ng2-b ng-if="showB2"></ng2-b>
`);
const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
// Wait for module A to be bootstrapped.
setTimeout(() => {
expect(multiTrim(element.textContent)).toBe('ng2A()');
// Wait for module A to be bootstrapped.
setTimeout(() => {
expect(multiTrim(element.textContent)).toBe('ng2A()');
// Nested component B.
$rootScope.$apply('showB1 = true');
// Nested component B.
$rootScope.$apply('showB1 = true');
// Wait for module B to be bootstrapped.
setTimeout(() => {
// It is debatable, whether the order of traversal should be:
// CompB > CompA > ModB > ModA > Plat (similar to how lazy-loaded components
// work)
expect(multiTrim(element.children[0].textContent))
.toBe(
'ng2A( FOO:CompB-foo BAR:CompA-bar BAZ:ModB-baz QUX:Plat-qux QUUX:Plat-quux )');
// Wait for module B to be bootstrapped.
setTimeout(() => {
// It is debatable, whether the order of traversal should be:
// CompB > CompA > ModB > ModA > Plat (similar to how lazy-loaded components
// work)
expect(multiTrim(element.children[0].textContent))
.toBe(
'ng2A( FOO:CompB-foo BAR:CompA-bar BAZ:ModB-baz QUX:Plat-qux QUUX:Plat-quux )');
// Standalone component B.
$rootScope.$apply('showB2 = true');
expect(multiTrim(element.children[1].textContent))
.toBe(
'FOO:CompB-foo BAR:ModB-bar BAZ:ModB-baz QUX:Plat-qux QUUX:Plat-quux');
});
});
}));
// Standalone component B.
$rootScope.$apply('showB2 = true');
expect(multiTrim(element.children[1].textContent))
.toBe('FOO:CompB-foo BAR:ModB-bar BAZ:ModB-baz QUX:Plat-qux QUUX:Plat-quux');
});
});
}));
it('should support downgrading a component and propagate inputs', async(() => {
@Component(