Skip to content

Commit cbd6264

Browse files
ocombejasonaden
authored andcommitted
fix(ivy): link correct ngModule's injector to the bootstrapped component (angular#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 angular#28183
1 parent 317cc92 commit cbd6264

File tree

4 files changed

+77
-63
lines changed

4 files changed

+77
-63
lines changed

packages/core/src/application_ref.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from './linker/ng_mod
2222
import {InternalViewRef, ViewRef} from './linker/view_ref';
2323
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
2424
import {assertNgModuleType} from './render3/assert';
25+
import {ComponentFactory as R3ComponentFactory} from './render3/component_ref';
2526
import {NgModuleFactory as R3NgModuleFactory} from './render3/ng_module_ref';
2627
import {Testability, TestabilityRegistry} from './testability/testability';
2728
import {isDevMode} from './util/is_dev_mode';
@@ -51,6 +52,16 @@ export function compileNgModuleFactory__POST_R3__<M>(
5152
return Promise.resolve(new R3NgModuleFactory(moduleType));
5253
}
5354

55+
let isBoundToModule: <C>(cf: ComponentFactory<C>) => boolean = isBoundToModule__PRE_R3__;
56+
57+
export function isBoundToModule__PRE_R3__<C>(cf: ComponentFactory<C>): boolean {
58+
return cf instanceof ComponentFactoryBoundToModule;
59+
}
60+
61+
export function isBoundToModule__POST_R3__<C>(cf: ComponentFactory<C>): boolean {
62+
return (cf as R3ComponentFactory<C>).isBoundToModule;
63+
}
64+
5465
export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken<boolean>('AllowMultipleToken');
5566

5667

@@ -467,9 +478,7 @@ export class ApplicationRef {
467478
this.componentTypes.push(componentFactory.componentType);
468479

469480
// Create a factory associated with the current module if it's not bound to some other
470-
const ngModule = componentFactory instanceof ComponentFactoryBoundToModule ?
471-
null :
472-
this._injector.get(NgModuleRef);
481+
const ngModule = isBoundToModule(componentFactory) ? null : this._injector.get(NgModuleRef);
473482
const selectorOrNode = rootSelectorOrNode || componentFactory.selector;
474483
const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule);
475484

packages/core/src/core_render3_private_export.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ export {
211211
//
212212
// no code actually imports these symbols from the @angular/core entry point
213213
export {
214-
compileNgModuleFactory__POST_R3__ as ɵcompileNgModuleFactory__POST_R3__
214+
compileNgModuleFactory__POST_R3__ as ɵcompileNgModuleFactory__POST_R3__,
215+
isBoundToModule__POST_R3__ as ɵisBoundToModule__POST_R3__
215216
} from './application_ref';
216217
export {
217218
SWITCH_COMPILE_COMPONENT__POST_R3__ as ɵSWITCH_COMPILE_COMPONENT__POST_R3__,

packages/core/src/render3/component_ref.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
102102
selector: string;
103103
componentType: Type<any>;
104104
ngContentSelectors: string[];
105+
isBoundToModule: boolean;
105106

106107
get inputs(): {propName: string; templateName: string;}[] {
107108
return toRefArray(this.componentDef.inputs);
@@ -124,6 +125,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
124125
// It is implicitly expected as the first item in the projectable nodes array.
125126
this.ngContentSelectors =
126127
componentDef.ngContentSelectors ? ['*', ...componentDef.ngContentSelectors] : [];
128+
this.isBoundToModule = !!ngModule;
127129
}
128130

129131
create(

packages/core/test/application_ref_spec.ts

Lines changed: 61 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
9+
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, InjectionToken, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
1010
import {ApplicationRef} from '@angular/core/src/application_ref';
1111
import {ErrorHandler} from '@angular/core/src/error_handler';
1212
import {ComponentRef} from '@angular/core/src/linker/component_factory';
@@ -15,7 +15,7 @@ import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
1515
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
1616
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
1717
import {expect} from '@angular/platform-browser/testing/src/matchers';
18-
import {fixmeIvy, ivyEnabled, modifiedInIvy} from '@angular/private/testing';
18+
import {ivyEnabled} from '@angular/private/testing';
1919

2020
import {NoopNgZone} from '../src/zone/ng_zone';
2121
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
@@ -74,63 +74,65 @@ class SomeComponent {
7474
return MyModule;
7575
}
7676

77-
fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running')
78-
.it('should bootstrap a component from a child module',
79-
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
80-
@Component({
81-
selector: 'bootstrap-app',
82-
template: '',
83-
})
84-
class SomeComponent {
85-
}
86-
87-
@NgModule({
88-
providers: [{provide: 'hello', useValue: 'component'}],
89-
declarations: [SomeComponent],
90-
entryComponents: [SomeComponent],
91-
})
92-
class SomeModule {
93-
}
94-
95-
createRootEl();
96-
const modFactory = compiler.compileModuleSync(SomeModule);
97-
const module = modFactory.create(TestBed);
98-
const cmpFactory =
99-
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
100-
const component = app.bootstrap(cmpFactory);
101-
102-
// The component should see the child module providers
103-
expect(component.injector.get('hello')).toEqual('component');
104-
})));
105-
106-
fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running')
107-
.it('should bootstrap a component with a custom selector',
108-
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
109-
@Component({
110-
selector: 'bootstrap-app',
111-
template: '',
112-
})
113-
class SomeComponent {
114-
}
115-
116-
@NgModule({
117-
providers: [{provide: 'hello', useValue: 'component'}],
118-
declarations: [SomeComponent],
119-
entryComponents: [SomeComponent],
120-
})
121-
class SomeModule {
122-
}
123-
124-
createRootEl('custom-selector');
125-
const modFactory = compiler.compileModuleSync(SomeModule);
126-
const module = modFactory.create(TestBed);
127-
const cmpFactory =
128-
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
129-
const component = app.bootstrap(cmpFactory, 'custom-selector');
130-
131-
// The component should see the child module providers
132-
expect(component.injector.get('hello')).toEqual('component');
133-
})));
77+
it('should bootstrap a component from a child module',
78+
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
79+
@Component({
80+
selector: 'bootstrap-app',
81+
template: '',
82+
})
83+
class SomeComponent {
84+
}
85+
86+
const helloToken = new InjectionToken<string>('hello');
87+
88+
@NgModule({
89+
providers: [{provide: helloToken, useValue: 'component'}],
90+
declarations: [SomeComponent],
91+
entryComponents: [SomeComponent],
92+
})
93+
class SomeModule {
94+
}
95+
96+
createRootEl();
97+
const modFactory = compiler.compileModuleSync(SomeModule);
98+
const module = modFactory.create(TestBed);
99+
const cmpFactory =
100+
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
101+
const component = app.bootstrap(cmpFactory);
102+
103+
// The component should see the child module providers
104+
expect(component.injector.get(helloToken)).toEqual('component');
105+
})));
106+
107+
it('should bootstrap a component with a custom selector',
108+
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
109+
@Component({
110+
selector: 'bootstrap-app',
111+
template: '',
112+
})
113+
class SomeComponent {
114+
}
115+
116+
const helloToken = new InjectionToken<string>('hello');
117+
118+
@NgModule({
119+
providers: [{provide: helloToken, useValue: 'component'}],
120+
declarations: [SomeComponent],
121+
entryComponents: [SomeComponent],
122+
})
123+
class SomeModule {
124+
}
125+
126+
createRootEl('custom-selector');
127+
const modFactory = compiler.compileModuleSync(SomeModule);
128+
const module = modFactory.create(TestBed);
129+
const cmpFactory =
130+
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
131+
const component = app.bootstrap(cmpFactory, 'custom-selector');
132+
133+
// The component should see the child module providers
134+
expect(component.injector.get(helloToken)).toEqual('component');
135+
})));
134136

135137
describe('ApplicationRef', () => {
136138
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });

0 commit comments

Comments
 (0)