Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): unable to NgModuleRef.injector in module constructor #35731

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 18 additions & 5 deletions packages/core/src/di/r3_injector.ts
Expand Up @@ -78,8 +78,21 @@ interface Record<T> {
export function createInjector(
defType: /* InjectorType<any> */ any, parent: Injector | null = null,
additionalProviders: StaticProvider[] | null = null, name?: string): Injector {
parent = parent || getNullInjector();
return new R3Injector(defType, additionalProviders, parent, name);
const injector =
createInjectorWithoutInjectorInstances(defType, parent, additionalProviders, name);
injector._resolveInjectorDefTypes();
return injector;
}

/**
* Creates a new injector without eagerly resolving its injector types. Can be used in places
* where resolving the injector types immediately can lead to an infinite loop. The injector types
* should be resolved at a later point by calling `_resolveInjectorDefTypes`.
*/
export function createInjectorWithoutInjectorInstances(
defType: /* InjectorType<any> */ any, parent: Injector | null = null,
additionalProviders: StaticProvider[] | null = null, name?: string): R3Injector {
return new R3Injector(defType, additionalProviders, parent || getNullInjector(), name);
}

export class R3Injector {
Expand Down Expand Up @@ -136,9 +149,6 @@ export class R3Injector {
const record = this.records.get(INJECTOR_SCOPE);
this.scope = record != null ? record.value : null;

// Eagerly instantiate the InjectorType classes themselves.
this.injectorDefTypes.forEach(defType => this.get(defType));

// Source name, used for debugging
this.source = source || (typeof def === 'object' ? null : stringify(def));
}
Expand Down Expand Up @@ -224,6 +234,9 @@ export class R3Injector {
}
}

/** @internal */
_resolveInjectorDefTypes() { this.injectorDefTypes.forEach(defType => this.get(defType)); }

toString() {
const tokens = <string[]>[], records = this.records;
records.forEach((v, token) => tokens.push(stringify(token)));
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/render3/ng_module_ref.ts
Expand Up @@ -9,7 +9,7 @@
import {Injector} from '../di/injector';
import {INJECTOR} from '../di/injector_compatibility';
import {InjectFlags} from '../di/interface/injector';
import {R3Injector, createInjector} from '../di/r3_injector';
import {R3Injector, createInjectorWithoutInjectorInstances} from '../di/r3_injector';
import {Type} from '../interface/type';
import {ComponentFactoryResolver as viewEngine_ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {InternalNgModuleRef, NgModuleFactory as viewEngine_NgModuleFactory, NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
Expand Down Expand Up @@ -52,13 +52,18 @@ export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements Interna
const ngLocaleIdDef = getNgLocaleIdDef(ngModuleType);
ngLocaleIdDef && setLocaleId(ngLocaleIdDef);
this._bootstrapComponents = maybeUnwrapFn(ngModuleDef !.bootstrap);
this._r3Injector = createInjector(
this._r3Injector = createInjectorWithoutInjectorInstances(
ngModuleType, _parent,
[
{provide: viewEngine_NgModuleRef, useValue: this},
{provide: viewEngine_ComponentFactoryResolver, useValue: this.componentFactoryResolver}
],
stringify(ngModuleType)) as R3Injector;

// We need to resolve the injector types separately from the injector creation, because
// the module might be trying to use this ref in its contructor for DI which will cause a
// circular error that will eventually error out, because the injector isn't created yet.
this._r3Injector._resolveInjectorDefTypes();
this.instance = this.get(ngModuleType);
}

Expand Down
30 changes: 25 additions & 5 deletions packages/core/test/acceptance/ng_module_spec.ts
Expand Up @@ -7,7 +7,7 @@
*/

import {CommonModule} from '@angular/common';
import {CUSTOM_ELEMENTS_SCHEMA, Component, ComponentFactory, Injectable, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element} from '@angular/core';
import {CUSTOM_ELEMENTS_SCHEMA, Component, Injectable, InjectionToken, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
Expand Down Expand Up @@ -475,9 +475,28 @@ describe('NgModule', () => {

});

it('should be able to use ComponentFactoryResolver from the NgModuleRef inside the module constructor',
it('should be able to use DI through the NgModuleRef inside the module constructor', () => {
const token = new InjectionToken<string>('token');
let value: string|undefined;

@NgModule({
imports: [CommonModule],
providers: [{provide: token, useValue: 'foo'}],
})
class TestModule {
constructor(ngRef: NgModuleRef<TestModule>) { value = ngRef.injector.get(token); }
}

TestBed.configureTestingModule({imports: [TestModule], declarations: [TestCmp]});
const fixture = TestBed.createComponent(TestCmp);
fixture.detectChanges();

expect(value).toBe('foo');
});

it('should be able to create a component through the ComponentFactoryResolver of an NgModuleRef in a module constructor',
() => {
let factory: ComponentFactory<TestCmp>;
let componentInstance: TestCmp|undefined;

@NgModule({
declarations: [TestCmp],
Expand All @@ -486,13 +505,14 @@ describe('NgModule', () => {
})
class MyModule {
constructor(ngModuleRef: NgModuleRef<any>) {
factory = ngModuleRef.componentFactoryResolver.resolveComponentFactory(TestCmp);
const factory = ngModuleRef.componentFactoryResolver.resolveComponentFactory(TestCmp);
componentInstance = factory.create(ngModuleRef.injector).instance;
}
}

TestBed.configureTestingModule({imports: [MyModule]});
TestBed.createComponent(TestCmp);
expect(factory !.componentType).toBe(TestCmp);
expect(componentInstance).toBeAnInstanceOf(TestCmp);
});

});
Expand Up @@ -107,6 +107,9 @@
{
"name": "createInjector"
},
{
"name": "createInjectorWithoutInjectorInstances"
},
{
"name": "deepForEach"
},
Expand Down Expand Up @@ -224,4 +227,4 @@
{
"name": "noSideEffects"
}
]
]