fix(core): inject() should always work in an NgModule injection scope (#23148)

Currently the context for inject() is only set when the token is seen
for the first time. This has two issues:

* It should always be set when injecting from that injector, because
  a constructor may wish to call inject() directly.
* If an NgModuleFactory is .create()'d twice, and an ngInjectableDef
  token is requested from each of them, the second time will fail.
  This is because the first injection adds the provider definition
  and calls the factory, and the provider definitions are shared.
  The second injector will see the provider definition and call the
  factory to create an instance, but without setting the correct
  context for inject().

Fixes angular/material2#10586.

PR Close #23148
This commit is contained in:
Alex Rickabaugh 2018-04-03 09:54:58 -07:00
parent 18ac228a27
commit fab6b39c3d
2 changed files with 48 additions and 41 deletions

View File

@ -75,51 +75,51 @@ export function initNgModule(data: NgModuleData) {
export function resolveNgModuleDep(
data: NgModuleData, depDef: DepDef, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
if (depDef.flags & DepFlags.Value) {
return depDef.token;
}
if (depDef.flags & DepFlags.Optional) {
notFoundValue = null;
}
if (depDef.flags & DepFlags.SkipSelf) {
return data._parent.get(depDef.token, notFoundValue);
}
const tokenKey = depDef.tokenKey;
switch (tokenKey) {
case InjectorRefTokenKey:
case INJECTORRefTokenKey:
case NgModuleRefTokenKey:
return data;
}
const providerDef = data._def.providersByKey[tokenKey];
if (providerDef) {
let providerInstance = data._providers[providerDef.index];
if (providerInstance === undefined) {
providerInstance = data._providers[providerDef.index] =
_createProviderInstance(data, providerDef);
const former = setCurrentInjector(data);
try {
if (depDef.flags & DepFlags.Value) {
return depDef.token;
}
return providerInstance === UNDEFINED_VALUE ? undefined : providerInstance;
} else if (depDef.token.ngInjectableDef && targetsModule(data, depDef.token.ngInjectableDef)) {
const injectableDef = depDef.token.ngInjectableDef as InjectableDef<any>;
const key = tokenKey;
const index = data._providers.length;
data._def.providersByKey[depDef.tokenKey] = {
flags: NodeFlags.TypeFactoryProvider | NodeFlags.LazyProvider,
value: injectableDef.factory,
deps: [], index,
token: depDef.token,
};
const former = setCurrentInjector(data);
try {
if (depDef.flags & DepFlags.Optional) {
notFoundValue = null;
}
if (depDef.flags & DepFlags.SkipSelf) {
return data._parent.get(depDef.token, notFoundValue);
}
const tokenKey = depDef.tokenKey;
switch (tokenKey) {
case InjectorRefTokenKey:
case INJECTORRefTokenKey:
case NgModuleRefTokenKey:
return data;
}
const providerDef = data._def.providersByKey[tokenKey];
if (providerDef) {
let providerInstance = data._providers[providerDef.index];
if (providerInstance === undefined) {
providerInstance = data._providers[providerDef.index] =
_createProviderInstance(data, providerDef);
}
return providerInstance === UNDEFINED_VALUE ? undefined : providerInstance;
} else if (depDef.token.ngInjectableDef && targetsModule(data, depDef.token.ngInjectableDef)) {
const injectableDef = depDef.token.ngInjectableDef as InjectableDef<any>;
const key = tokenKey;
const index = data._providers.length;
data._def.providersByKey[depDef.tokenKey] = {
flags: NodeFlags.TypeFactoryProvider | NodeFlags.LazyProvider,
value: injectableDef.factory,
deps: [], index,
token: depDef.token,
};
data._providers[index] = UNDEFINED_VALUE;
return (
data._providers[index] =
_createProviderInstance(data, data._def.providersByKey[depDef.tokenKey]));
} finally {
setCurrentInjector(former);
}
return data._parent.get(depDef.token, notFoundValue);
} finally {
setCurrentInjector(former);
}
return data._parent.get(depDef.token, notFoundValue);
}
function moduleTransitivelyPresent(ngModule: NgModuleData, scope: any): boolean {

View File

@ -8,7 +8,7 @@
import {NgModuleRef} from '@angular/core';
import {InjectableDef, defineInjectable} from '@angular/core/src/di/defs';
import {InjectFlags, Injector, inject} from '@angular/core/src/di/injector';
import {INJECTOR, InjectFlags, Injector, inject} from '@angular/core/src/di/injector';
import {makePropDecorator} from '@angular/core/src/util/decorators';
import {NgModuleDefinition, NgModuleProviderDef, NodeFlags} from '@angular/core/src/view';
import {moduleDef, moduleProvideDef, resolveNgModuleDep} from '@angular/core/src/view/ng_module';
@ -88,6 +88,10 @@ class FromChildWithSkipSelfDep {
});
}
class UsesInject {
constructor() { inject(INJECTOR); }
}
function makeProviders(classes: any[], modules: any[]): NgModuleDefinition {
const providers =
classes.map((token, index) => ({
@ -105,8 +109,8 @@ describe('NgModuleRef_ injector', () => {
let ref: NgModuleRef<any>;
let childRef: NgModuleRef<any>;
beforeEach(() => {
ref =
createNgModuleRef(MyModule, Injector.NULL, [], makeProviders([MyModule, Foo], [MyModule]));
ref = createNgModuleRef(
MyModule, Injector.NULL, [], makeProviders([MyModule, Foo, UsesInject], [MyModule]));
childRef = createNgModuleRef(
MyChildModule, ref.injector, [], makeProviders([MyChildModule], [MyChildModule]));
});
@ -147,4 +151,7 @@ describe('NgModuleRef_ injector', () => {
it('does not inject something not scoped to the module',
() => { expect(ref.injector.get(Baz, null)).toBeNull(); });
it('injects with the current injector always set',
() => { expect(() => ref.injector.get(UsesInject)).not.toThrow(); });
});