fix(core): take @Host into account while processing `useFactory` arguments (#40122)
DI providers can be defined via `useFactory` function, which may have arguments configured via `deps` array. The `deps` array may contain DI flags represented by DI decorators (such as `@Self`, `@SkipSelf`, etc). Prior to this commit, having the `@Host` decorator in `deps` array resulted in runtime error in Ivy. The problem was that the `@Host` decorator was not taken into account while `useFactory` argument list was constructed, the `@Host` decorator was treated as a token that should be looked up. This commit updates the logic which prepares `useFactory` arguments to recognize the `@Host` decorator. PR Close #40122
This commit is contained in:
parent
212245f197
commit
3735633bb0
|
@ -3,7 +3,7 @@
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 1485,
|
"runtime-es2015": 1485,
|
||||||
"main-es2015": 140333,
|
"main-es2015": 140871,
|
||||||
"polyfills-es2015": 36964
|
"polyfills-es2015": 36964
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,14 @@ import '../util/ng_dev_mode';
|
||||||
import {AbstractType, Type} from '../interface/type';
|
import {AbstractType, Type} from '../interface/type';
|
||||||
import {getClosureSafeProperty} from '../util/property';
|
import {getClosureSafeProperty} from '../util/property';
|
||||||
import {stringify} from '../util/stringify';
|
import {stringify} from '../util/stringify';
|
||||||
|
|
||||||
import {resolveForwardRef} from './forward_ref';
|
import {resolveForwardRef} from './forward_ref';
|
||||||
import {getInjectImplementation, injectRootLimpMode} from './inject_switch';
|
import {getInjectImplementation, injectRootLimpMode} from './inject_switch';
|
||||||
import {InjectionToken} from './injection_token';
|
import {InjectionToken} from './injection_token';
|
||||||
import {Injector} from './injector';
|
import {Injector} from './injector';
|
||||||
import {InjectFlags} from './interface/injector';
|
import {InjectFlags} from './interface/injector';
|
||||||
import {ValueProvider} from './interface/provider';
|
import {ValueProvider} from './interface/provider';
|
||||||
import {Inject, Optional, Self, SkipSelf} from './metadata';
|
import {Host, Inject, Optional, Self, SkipSelf} from './metadata';
|
||||||
|
|
||||||
|
|
||||||
const _THROW_IF_NOT_FOUND = {};
|
const _THROW_IF_NOT_FOUND = {};
|
||||||
|
@ -151,6 +152,8 @@ export function injectArgs(types: (Type<any>|InjectionToken<any>|any[])[]): any[
|
||||||
flags |= InjectFlags.SkipSelf;
|
flags |= InjectFlags.SkipSelf;
|
||||||
} else if (meta instanceof Self || meta.ngMetadataName === 'Self' || meta === Self) {
|
} else if (meta instanceof Self || meta.ngMetadataName === 'Self' || meta === Self) {
|
||||||
flags |= InjectFlags.Self;
|
flags |= InjectFlags.Self;
|
||||||
|
} else if (meta instanceof Host || meta.ngMetadataName === 'Host' || meta === Host) {
|
||||||
|
flags |= InjectFlags.Host;
|
||||||
} else if (meta instanceof Inject || meta === Inject) {
|
} else if (meta instanceof Inject || meta === Inject) {
|
||||||
type = meta.token;
|
type = meta.token;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2803,6 +2803,106 @@ describe('di', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to use Host in `useFactory` dependency config', () => {
|
||||||
|
// Scenario:
|
||||||
|
// ---------
|
||||||
|
// <root (provides token A)>
|
||||||
|
// <comp (provides token B via useFactory(@Host() @Inject(A))></comp>
|
||||||
|
// </root>
|
||||||
|
@Component({
|
||||||
|
selector: 'root',
|
||||||
|
template: '<comp></comp>',
|
||||||
|
viewProviders: [{
|
||||||
|
provide: 'A',
|
||||||
|
useValue: 'A from Root',
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
class Root {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '{{ token }}',
|
||||||
|
viewProviders: [{
|
||||||
|
provide: 'B',
|
||||||
|
deps: [[new Inject('A'), new Host()]],
|
||||||
|
useFactory: (token: string) => `${token} (processed by useFactory)`,
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
class Comp {
|
||||||
|
constructor(@Inject('B') readonly token: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `<root></root>`,
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Root, Comp, App]});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement.textContent).toBe('A from Root (processed by useFactory)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not lookup outside of the host element when Host is used in `useFactory`', () => {
|
||||||
|
// Scenario:
|
||||||
|
// ---------
|
||||||
|
// <root (provides token A)>
|
||||||
|
// <intermediate>
|
||||||
|
// <comp (provides token B via useFactory(@Host() @Inject(A))></comp>
|
||||||
|
// </intermediate>
|
||||||
|
// </root>
|
||||||
|
@Component({
|
||||||
|
selector: 'root',
|
||||||
|
template: '<intermediate></intermediate>',
|
||||||
|
viewProviders: [{
|
||||||
|
provide: 'A',
|
||||||
|
useValue: 'A from Root',
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
class Root {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'intermediate',
|
||||||
|
template: '<comp></comp>',
|
||||||
|
})
|
||||||
|
class Intermediate {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'comp',
|
||||||
|
template: '{{ token }}',
|
||||||
|
viewProviders: [{
|
||||||
|
provide: 'B',
|
||||||
|
deps: [[new Inject('A'), new Host(), new Optional()]],
|
||||||
|
useFactory: (token: string) =>
|
||||||
|
token ? `${token} (processed by useFactory)` : 'No token A found',
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
class Comp {
|
||||||
|
constructor(@Inject('B') readonly token: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `<root></root>`,
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Root, Comp, App, Intermediate]});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
// Making sure that the `@Host` takes effect and token `A` becomes unavailable in DI since it's
|
||||||
|
// defined one level up from the Comp's host view.
|
||||||
|
expect(fixture.nativeElement.textContent).toBe('No token A found');
|
||||||
|
});
|
||||||
|
|
||||||
it('should not cause cyclic dependency if same token is requested in deps with @SkipSelf', () => {
|
it('should not cause cyclic dependency if same token is requested in deps with @SkipSelf', () => {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-comp',
|
selector: 'my-comp',
|
||||||
|
|
|
@ -236,6 +236,9 @@
|
||||||
{
|
{
|
||||||
"name": "FormsModule"
|
"name": "FormsModule"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Host"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "INJECTOR"
|
"name": "INJECTOR"
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
{
|
{
|
||||||
"name": "EMPTY_ARRAY"
|
"name": "EMPTY_ARRAY"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Host"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "INJECTOR"
|
"name": "INJECTOR"
|
||||||
},
|
},
|
||||||
|
|
|
@ -290,6 +290,9 @@
|
||||||
{
|
{
|
||||||
"name": "HashLocationStrategy"
|
"name": "HashLocationStrategy"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Host"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "INITIAL_VALUE"
|
"name": "INITIAL_VALUE"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue