fix(ivy): link correct ngModule's injector to the bootstrapped component (#28183)

Previously, bootstrapping a component with render3 would create a chained injector with the test bed ngModule instead of the ngModule that the component belongs to.
Now when a component belongs to an ngModule, we use that for the chained injector, ensuring the correct injection of any providers that this ngModule contains.

FW-776 #resolve

PR Close #28183
This commit is contained in:
Olivier Combe 2019-01-16 15:42:13 +01:00 committed by Jason Aden
parent 317cc922ac
commit cbd626413c
4 changed files with 70 additions and 56 deletions

View File

@ -22,6 +22,7 @@ import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from './linker/ng_mod
import {InternalViewRef, ViewRef} from './linker/view_ref'; import {InternalViewRef, ViewRef} from './linker/view_ref';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile'; import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
import {assertNgModuleType} from './render3/assert'; import {assertNgModuleType} from './render3/assert';
import {ComponentFactory as R3ComponentFactory} from './render3/component_ref';
import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref'; import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
import {Testability, TestabilityRegistry} from './testability/testability'; import {Testability, TestabilityRegistry} from './testability/testability';
import {isDevMode} from './util/is_dev_mode'; import {isDevMode} from './util/is_dev_mode';
@ -51,6 +52,16 @@ export function compileNgModuleFactory__POST_R3__<M>(
return Promise.resolve(new R3NgModuleFactory(moduleType)); return Promise.resolve(new R3NgModuleFactory(moduleType));
} }
let isBoundToModule: <C>(cf: ComponentFactory<C>) => boolean = isBoundToModule__PRE_R3__;
export function isBoundToModule__PRE_R3__<C>(cf: ComponentFactory<C>): boolean {
return cf instanceof ComponentFactoryBoundToModule;
}
export function isBoundToModule__POST_R3__<C>(cf: ComponentFactory<C>): boolean {
return (cf as R3ComponentFactory<C>).isBoundToModule;
}
export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken<boolean>('AllowMultipleToken'); export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken<boolean>('AllowMultipleToken');
@ -467,9 +478,7 @@ export class ApplicationRef {
this.componentTypes.push(componentFactory.componentType); this.componentTypes.push(componentFactory.componentType);
// Create a factory associated with the current module if it's not bound to some other // Create a factory associated with the current module if it's not bound to some other
const ngModule = componentFactory instanceof ComponentFactoryBoundToModule ? const ngModule = isBoundToModule(componentFactory) ? null : this._injector.get(NgModuleRef);
null :
this._injector.get(NgModuleRef);
const selectorOrNode = rootSelectorOrNode || componentFactory.selector; const selectorOrNode = rootSelectorOrNode || componentFactory.selector;
const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule); const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule);

View File

@ -211,7 +211,8 @@ export {
// //
// no code actually imports these symbols from the @angular/core entry point // no code actually imports these symbols from the @angular/core entry point
export { export {
compileNgModuleFactory__POST_R3__ as ɵcompileNgModuleFactory__POST_R3__ compileNgModuleFactory__POST_R3__ as ɵcompileNgModuleFactory__POST_R3__,
isBoundToModule__POST_R3__ as ɵisBoundToModule__POST_R3__
} from './application_ref'; } from './application_ref';
export { export {
SWITCH_COMPILE_COMPONENT__POST_R3__ as ɵSWITCH_COMPILE_COMPONENT__POST_R3__, SWITCH_COMPILE_COMPONENT__POST_R3__ as ɵSWITCH_COMPILE_COMPONENT__POST_R3__,

View File

@ -102,6 +102,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
selector: string; selector: string;
componentType: Type<any>; componentType: Type<any>;
ngContentSelectors: string[]; ngContentSelectors: string[];
isBoundToModule: boolean;
get inputs(): {propName: string; templateName: string;}[] { get inputs(): {propName: string; templateName: string;}[] {
return toRefArray(this.componentDef.inputs); return toRefArray(this.componentDef.inputs);
@ -124,6 +125,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
// It is implicitly expected as the first item in the projectable nodes array. // It is implicitly expected as the first item in the projectable nodes array.
this.ngContentSelectors = this.ngContentSelectors =
componentDef.ngContentSelectors ? ['*', ...componentDef.ngContentSelectors] : []; componentDef.ngContentSelectors ? ['*', ...componentDef.ngContentSelectors] : [];
this.isBoundToModule = !!ngModule;
} }
create( create(

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, InjectionToken, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
import {ApplicationRef} from '@angular/core/src/application_ref'; import {ApplicationRef} from '@angular/core/src/application_ref';
import {ErrorHandler} from '@angular/core/src/error_handler'; import {ErrorHandler} from '@angular/core/src/error_handler';
import {ComponentRef} from '@angular/core/src/linker/component_factory'; import {ComponentRef} from '@angular/core/src/linker/component_factory';
@ -15,7 +15,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens'; import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {fixmeIvy, ivyEnabled, modifiedInIvy} from '@angular/private/testing'; import {ivyEnabled} from '@angular/private/testing';
import {NoopNgZone} from '../src/zone/ng_zone'; import {NoopNgZone} from '../src/zone/ng_zone';
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing'; import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
@ -74,63 +74,65 @@ class SomeComponent {
return MyModule; return MyModule;
} }
fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running') it('should bootstrap a component from a child module',
.it('should bootstrap a component from a child module', async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { @Component({
@Component({ selector: 'bootstrap-app',
selector: 'bootstrap-app', template: '',
template: '', })
}) class SomeComponent {
class SomeComponent { }
}
@NgModule({ const helloToken = new InjectionToken<string>('hello');
providers: [{provide: 'hello', useValue: 'component'}],
declarations: [SomeComponent],
entryComponents: [SomeComponent],
})
class SomeModule {
}
createRootEl(); @NgModule({
const modFactory = compiler.compileModuleSync(SomeModule); providers: [{provide: helloToken, useValue: 'component'}],
const module = modFactory.create(TestBed); declarations: [SomeComponent],
const cmpFactory = entryComponents: [SomeComponent],
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !; })
const component = app.bootstrap(cmpFactory); class SomeModule {
}
// The component should see the child module providers createRootEl();
expect(component.injector.get('hello')).toEqual('component'); const modFactory = compiler.compileModuleSync(SomeModule);
}))); const module = modFactory.create(TestBed);
const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
const component = app.bootstrap(cmpFactory);
fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running') // The component should see the child module providers
.it('should bootstrap a component with a custom selector', expect(component.injector.get(helloToken)).toEqual('component');
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { })));
@Component({
selector: 'bootstrap-app',
template: '',
})
class SomeComponent {
}
@NgModule({ it('should bootstrap a component with a custom selector',
providers: [{provide: 'hello', useValue: 'component'}], async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
declarations: [SomeComponent], @Component({
entryComponents: [SomeComponent], selector: 'bootstrap-app',
}) template: '',
class SomeModule { })
} class SomeComponent {
}
createRootEl('custom-selector'); const helloToken = new InjectionToken<string>('hello');
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
const component = app.bootstrap(cmpFactory, 'custom-selector');
// The component should see the child module providers @NgModule({
expect(component.injector.get('hello')).toEqual('component'); providers: [{provide: helloToken, useValue: 'component'}],
}))); declarations: [SomeComponent],
entryComponents: [SomeComponent],
})
class SomeModule {
}
createRootEl('custom-selector');
const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed);
const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
const component = app.bootstrap(cmpFactory, 'custom-selector');
// The component should see the child module providers
expect(component.injector.get(helloToken)).toEqual('component');
})));
describe('ApplicationRef', () => { describe('ApplicationRef', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); }); beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });