fix(ivy): provided Injector should be instantiated by its factory (#27456)
(FW-777) When an Injector is provided, R3Injector instantiates it by calling its constructor instead of its factory, not resolving dependencies. With this fix, the ngInjectorDef is checked and the factory is correctly used if it is found. PR Close #27456
This commit is contained in:
parent
486f69fcac
commit
8973f12ee4
|
@ -166,7 +166,7 @@ export class R3Injector {
|
||||||
if (def && this.injectableDefInScope(def)) {
|
if (def && this.injectableDefInScope(def)) {
|
||||||
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
|
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
|
||||||
// all along.
|
// all along.
|
||||||
record = makeRecord(injectableDefFactory(token), NOT_YET);
|
record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET);
|
||||||
this.records.set(token, record);
|
this.records.set(token, record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,9 +339,14 @@ export class R3Injector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectableDefFactory(token: Type<any>| InjectionToken<any>): () => any {
|
function injectableDefOrInjectorDefFactory(token: Type<any>| InjectionToken<any>): () => any {
|
||||||
const injectableDef = getInjectableDef(token as InjectableType<any>);
|
const injectableDef = getInjectableDef(token as InjectableType<any>);
|
||||||
if (injectableDef === null) {
|
if (injectableDef === null) {
|
||||||
|
const injectorDef = getInjectorDef(token as InjectorType<any>);
|
||||||
|
if (injectorDef !== null) {
|
||||||
|
return injectorDef.factory;
|
||||||
|
}
|
||||||
|
|
||||||
if (token instanceof InjectionToken) {
|
if (token instanceof InjectionToken) {
|
||||||
throw new Error(`Token ${stringify(token)} is missing an ngInjectableDef definition.`);
|
throw new Error(`Token ${stringify(token)} is missing an ngInjectableDef definition.`);
|
||||||
}
|
}
|
||||||
|
@ -369,7 +374,7 @@ function providerToRecord(provider: SingleProvider): Record<any> {
|
||||||
export function providerToFactory(provider: SingleProvider): () => any {
|
export function providerToFactory(provider: SingleProvider): () => any {
|
||||||
let factory: (() => any)|undefined = undefined;
|
let factory: (() => any)|undefined = undefined;
|
||||||
if (isTypeProvider(provider)) {
|
if (isTypeProvider(provider)) {
|
||||||
return injectableDefFactory(resolveForwardRef(provider));
|
return injectableDefOrInjectorDefFactory(resolveForwardRef(provider));
|
||||||
} else {
|
} else {
|
||||||
if (isValueProvider(provider)) {
|
if (isValueProvider(provider)) {
|
||||||
factory = () => resolveForwardRef(provider.useValue);
|
factory = () => resolveForwardRef(provider.useValue);
|
||||||
|
@ -383,7 +388,7 @@ export function providerToFactory(provider: SingleProvider): () => any {
|
||||||
if (hasDeps(provider)) {
|
if (hasDeps(provider)) {
|
||||||
factory = () => new (classRef)(...injectArgs(provider.deps));
|
factory = () => new (classRef)(...injectArgs(provider.deps));
|
||||||
} else {
|
} else {
|
||||||
return injectableDefFactory(classRef);
|
return injectableDefOrInjectorDefFactory(classRef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1008,7 +1008,7 @@
|
||||||
"name": "injectRootLimpMode"
|
"name": "injectRootLimpMode"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "injectableDefFactory"
|
"name": "injectableDefOrInjectorDefFactory"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "insertBloom"
|
"name": "insertBloom"
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
"name": "injectRootLimpMode"
|
"name": "injectRootLimpMode"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "injectableDefFactory"
|
"name": "injectableDefOrInjectorDefFactory"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "isExistingProvider"
|
"name": "isExistingProvider"
|
||||||
|
|
|
@ -2082,7 +2082,7 @@
|
||||||
"name": "injectViewContainerRef"
|
"name": "injectViewContainerRef"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "injectableDefFactory"
|
"name": "injectableDefOrInjectorDefFactory"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "insertBloom"
|
"name": "insertBloom"
|
||||||
|
|
|
@ -121,6 +121,14 @@ describe('InjectorDef-based createInjector()', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InjectorWithDep {
|
||||||
|
constructor(readonly service: Service) {}
|
||||||
|
|
||||||
|
static ngInjectorDef = defineInjector({
|
||||||
|
factory: () => new InjectorWithDep(inject(Service)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class Module {
|
class Module {
|
||||||
static ngInjectorDef = defineInjector({
|
static ngInjectorDef = defineInjector({
|
||||||
factory: () => new Module(),
|
factory: () => new Module(),
|
||||||
|
@ -137,6 +145,7 @@ describe('InjectorDef-based createInjector()', () => {
|
||||||
CircularA,
|
CircularA,
|
||||||
CircularB,
|
CircularB,
|
||||||
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
|
{provide: STATIC_TOKEN, useClass: StaticService, deps: [Service]},
|
||||||
|
InjectorWithDep,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -204,6 +213,12 @@ describe('InjectorDef-based createInjector()', () => {
|
||||||
expect(instance.locale).toEqual(['en', 'es']);
|
expect(instance.locale).toEqual(['en', 'es']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('injects an injector with dependencies', () => {
|
||||||
|
const instance = injector.get(InjectorWithDep);
|
||||||
|
expect(instance instanceof InjectorWithDep);
|
||||||
|
expect(instance.service).toBe(injector.get(Service));
|
||||||
|
});
|
||||||
|
|
||||||
it('injects a token with useExisting', () => {
|
it('injects a token with useExisting', () => {
|
||||||
const instance = injector.get(SERVICE_TOKEN);
|
const instance = injector.get(SERVICE_TOKEN);
|
||||||
expect(instance).toBe(injector.get(Service));
|
expect(instance).toBe(injector.get(Service));
|
||||||
|
|
|
@ -44,63 +44,59 @@ describe('setUpLocationSync', () => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-777: R3Injector doesn\'t support injectors as a provider') &&
|
it('should get the $rootScope from AngularJS and set an $on watch on $locationChangeStart',
|
||||||
it('should get the $rootScope from AngularJS and set an $on watch on $locationChangeStart',
|
() => {
|
||||||
() => {
|
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
|
||||||
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
|
|
||||||
|
|
||||||
upgradeModule.$injector.get.and.callFake(
|
upgradeModule.$injector.get.and.callFake(
|
||||||
(name: string) => (name === '$rootScope') && $rootScope);
|
(name: string) => (name === '$rootScope') && $rootScope);
|
||||||
|
|
||||||
setUpLocationSync(upgradeModule);
|
setUpLocationSync(upgradeModule);
|
||||||
|
|
||||||
expect($rootScope.$on).toHaveBeenCalledTimes(1);
|
expect($rootScope.$on).toHaveBeenCalledTimes(1);
|
||||||
expect($rootScope.$on)
|
expect($rootScope.$on).toHaveBeenCalledWith('$locationChangeStart', jasmine.any(Function));
|
||||||
.toHaveBeenCalledWith('$locationChangeStart', jasmine.any(Function));
|
});
|
||||||
});
|
|
||||||
|
|
||||||
fixmeIvy('FW-777: R3Injector doesn\'t support injectors as a provider') &&
|
it('should navigate by url every time $locationChangeStart is broadcasted', () => {
|
||||||
it('should navigate by url every time $locationChangeStart is broadcasted', () => {
|
const url = 'https://google.com';
|
||||||
const url = 'https://google.com';
|
const pathname = '/custom/route';
|
||||||
const pathname = '/custom/route';
|
const normalizedPathname = 'foo';
|
||||||
const normalizedPathname = 'foo';
|
const query = '?query=1&query2=3';
|
||||||
const query = '?query=1&query2=3';
|
const hash = '#new/hash';
|
||||||
const hash = '#new/hash';
|
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
|
||||||
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
|
|
||||||
|
|
||||||
upgradeModule.$injector.get.and.returnValue($rootScope);
|
upgradeModule.$injector.get.and.returnValue($rootScope);
|
||||||
LocationMock.normalize.and.returnValue(normalizedPathname);
|
LocationMock.normalize.and.returnValue(normalizedPathname);
|
||||||
|
|
||||||
setUpLocationSync(upgradeModule);
|
setUpLocationSync(upgradeModule);
|
||||||
|
|
||||||
const callback = $rootScope.$on.calls.argsFor(0)[1];
|
const callback = $rootScope.$on.calls.argsFor(0)[1];
|
||||||
callback({}, url + pathname + query + hash, '');
|
callback({}, url + pathname + query + hash, '');
|
||||||
|
|
||||||
expect(LocationMock.normalize).toHaveBeenCalledTimes(1);
|
expect(LocationMock.normalize).toHaveBeenCalledTimes(1);
|
||||||
expect(LocationMock.normalize).toHaveBeenCalledWith(pathname);
|
expect(LocationMock.normalize).toHaveBeenCalledWith(pathname);
|
||||||
|
|
||||||
expect(RouterMock.navigateByUrl).toHaveBeenCalledTimes(1);
|
expect(RouterMock.navigateByUrl).toHaveBeenCalledTimes(1);
|
||||||
expect(RouterMock.navigateByUrl).toHaveBeenCalledWith(normalizedPathname + query + hash);
|
expect(RouterMock.navigateByUrl).toHaveBeenCalledWith(normalizedPathname + query + hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-777: R3Injector doesn\'t support injectors as a provider') &&
|
it('should work correctly on browsers that do not start pathname with `/`', () => {
|
||||||
it('should work correctly on browsers that do not start pathname with `/`', () => {
|
const anchorProto = HTMLAnchorElement.prototype;
|
||||||
const anchorProto = HTMLAnchorElement.prototype;
|
const originalDescriptor = Object.getOwnPropertyDescriptor(anchorProto, 'pathname');
|
||||||
const originalDescriptor = Object.getOwnPropertyDescriptor(anchorProto, 'pathname');
|
Object.defineProperty(anchorProto, 'pathname', {get: () => 'foo/bar'});
|
||||||
Object.defineProperty(anchorProto, 'pathname', {get: () => 'foo/bar'});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
|
const $rootScope = jasmine.createSpyObj('$rootScope', ['$on']);
|
||||||
upgradeModule.$injector.get.and.returnValue($rootScope);
|
upgradeModule.$injector.get.and.returnValue($rootScope);
|
||||||
|
|
||||||
setUpLocationSync(upgradeModule);
|
setUpLocationSync(upgradeModule);
|
||||||
|
|
||||||
const callback = $rootScope.$on.calls.argsFor(0)[1];
|
const callback = $rootScope.$on.calls.argsFor(0)[1];
|
||||||
callback({}, '', '');
|
callback({}, '', '');
|
||||||
|
|
||||||
expect(LocationMock.normalize).toHaveBeenCalledWith('/foo/bar');
|
expect(LocationMock.normalize).toHaveBeenCalledWith('/foo/bar');
|
||||||
} finally {
|
} finally {
|
||||||
Object.defineProperty(anchorProto, 'pathname', originalDescriptor !);
|
Object.defineProperty(anchorProto, 'pathname', originalDescriptor !);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue