From cbd626413c13266c8acdd8400d38c516c47faaa2 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Wed, 16 Jan 2019 15:42:13 +0100 Subject: [PATCH] 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 --- packages/core/src/application_ref.ts | 15 ++- .../core/src/core_render3_private_export.ts | 3 +- packages/core/src/render3/component_ref.ts | 2 + packages/core/test/application_ref_spec.ts | 106 +++++++++--------- 4 files changed, 70 insertions(+), 56 deletions(-) diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index b7bbdb923f..22acad53c1 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -22,6 +22,7 @@ import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from './linker/ng_mod import {InternalViewRef, ViewRef} from './linker/view_ref'; import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile'; import {assertNgModuleType} from './render3/assert'; +import {ComponentFactory as R3ComponentFactory} from './render3/component_ref'; import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref'; import {Testability, TestabilityRegistry} from './testability/testability'; import {isDevMode} from './util/is_dev_mode'; @@ -51,6 +52,16 @@ export function compileNgModuleFactory__POST_R3__( return Promise.resolve(new R3NgModuleFactory(moduleType)); } +let isBoundToModule: (cf: ComponentFactory) => boolean = isBoundToModule__PRE_R3__; + +export function isBoundToModule__PRE_R3__(cf: ComponentFactory): boolean { + return cf instanceof ComponentFactoryBoundToModule; +} + +export function isBoundToModule__POST_R3__(cf: ComponentFactory): boolean { + return (cf as R3ComponentFactory).isBoundToModule; +} + export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken('AllowMultipleToken'); @@ -467,9 +478,7 @@ export class ApplicationRef { this.componentTypes.push(componentFactory.componentType); // Create a factory associated with the current module if it's not bound to some other - const ngModule = componentFactory instanceof ComponentFactoryBoundToModule ? - null : - this._injector.get(NgModuleRef); + const ngModule = isBoundToModule(componentFactory) ? null : this._injector.get(NgModuleRef); const selectorOrNode = rootSelectorOrNode || componentFactory.selector; const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule); diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 745031f2cd..3ff5ca0176 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -211,7 +211,8 @@ export { // // no code actually imports these symbols from the @angular/core entry point 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'; export { SWITCH_COMPILE_COMPONENT__POST_R3__ as ɵSWITCH_COMPILE_COMPONENT__POST_R3__, diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 9945c57f40..a34b15181e 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -102,6 +102,7 @@ export class ComponentFactory extends viewEngine_ComponentFactory { selector: string; componentType: Type; ngContentSelectors: string[]; + isBoundToModule: boolean; get inputs(): {propName: string; templateName: string;}[] { return toRefArray(this.componentDef.inputs); @@ -124,6 +125,7 @@ export class ComponentFactory extends viewEngine_ComponentFactory { // It is implicitly expected as the first item in the projectable nodes array. this.ngContentSelectors = componentDef.ngContentSelectors ? ['*', ...componentDef.ngContentSelectors] : []; + this.isBoundToModule = !!ngModule; } create( diff --git a/packages/core/test/application_ref_spec.ts b/packages/core/test/application_ref_spec.ts index 5a5f9e38c8..48f5544863 100644 --- a/packages/core/test/application_ref_spec.ts +++ b/packages/core/test/application_ref_spec.ts @@ -6,7 +6,7 @@ * 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 {ErrorHandler} from '@angular/core/src/error_handler'; 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 {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; 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 {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing'; @@ -74,63 +74,65 @@ class SomeComponent { return MyModule; } - fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running') - .it('should bootstrap a component from a child module', - async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { - @Component({ - selector: 'bootstrap-app', - template: '', - }) - class SomeComponent { - } + it('should bootstrap a component from a child module', + async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { + @Component({ + selector: 'bootstrap-app', + template: '', + }) + class SomeComponent { + } - @NgModule({ - providers: [{provide: 'hello', useValue: 'component'}], - declarations: [SomeComponent], - entryComponents: [SomeComponent], - }) - class SomeModule { - } + const helloToken = new InjectionToken('hello'); - createRootEl(); - const modFactory = compiler.compileModuleSync(SomeModule); - const module = modFactory.create(TestBed); - const cmpFactory = - module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !; - const component = app.bootstrap(cmpFactory); + @NgModule({ + providers: [{provide: helloToken, useValue: 'component'}], + declarations: [SomeComponent], + entryComponents: [SomeComponent], + }) + class SomeModule { + } - // The component should see the child module providers - expect(component.injector.get('hello')).toEqual('component'); - }))); + createRootEl(); + 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') - .it('should bootstrap a component with a custom selector', - async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { - @Component({ - selector: 'bootstrap-app', - template: '', - }) - class SomeComponent { - } + // The component should see the child module providers + expect(component.injector.get(helloToken)).toEqual('component'); + }))); - @NgModule({ - providers: [{provide: 'hello', useValue: 'component'}], - declarations: [SomeComponent], - entryComponents: [SomeComponent], - }) - class SomeModule { - } + it('should bootstrap a component with a custom selector', + async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { + @Component({ + selector: 'bootstrap-app', + template: '', + }) + class SomeComponent { + } - 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'); + const helloToken = new InjectionToken('hello'); - // The component should see the child module providers - expect(component.injector.get('hello')).toEqual('component'); - }))); + @NgModule({ + 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', () => { beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });