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:
parent
317cc922ac
commit
cbd626413c
@ -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);
|
||||||
|
|
||||||
|
@ -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__,
|
||||||
|
@ -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(
|
||||||
|
@ -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()]}); });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user