fix(ivy): support string tokens in dependency injection (#27383)
In Angular, it used to be an accepted practice to use strings as dependency injection tokens. E.g. {provide: 'test', useValue: 'provided'}. However, the Ivy node injection system did not support this. The Ivy DI system attempts to patch a Bloom bit index onto each type registered with it, and this patch operation does not work for a string token. This commit adds string token support to the bloom filter system by reserving bit 0 for string tokens. This eliminates the need for each string token to store its own Bloom bit, at the expense of slightly more expensive lookups of string tokens. PR Close #27383
This commit is contained in:
parent
c96dea27ce
commit
64a34616d8
|
@ -87,9 +87,10 @@ let nextNgElementId = 0;
|
||||||
* @param type The directive token to register
|
* @param type The directive token to register
|
||||||
*/
|
*/
|
||||||
export function bloomAdd(
|
export function bloomAdd(
|
||||||
injectorIndex: number, tView: TView, type: Type<any>| InjectionToken<any>): void {
|
injectorIndex: number, tView: TView, type: Type<any>| InjectionToken<any>| string): void {
|
||||||
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'expected firstTemplatePass to be true');
|
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'expected firstTemplatePass to be true');
|
||||||
let id: number|undefined = (type as any)[NG_ELEMENT_ID];
|
let id: number|undefined =
|
||||||
|
typeof type !== 'string' ? (type as any)[NG_ELEMENT_ID] : type.charCodeAt(0) || 0;
|
||||||
|
|
||||||
// Set a unique ID on the directive type, so if something tries to inject the directive,
|
// Set a unique ID on the directive type, so if something tries to inject the directive,
|
||||||
// we can easily retrieve the ID and hash it into the bloom bit that should be checked.
|
// we can easily retrieve the ID and hash it into the bloom bit that should be checked.
|
||||||
|
@ -502,9 +503,12 @@ export function getNodeInjectable(
|
||||||
* @param token the injection token
|
* @param token the injection token
|
||||||
* @returns the matching bit to check in the bloom filter or `null` if the token is not known.
|
* @returns the matching bit to check in the bloom filter or `null` if the token is not known.
|
||||||
*/
|
*/
|
||||||
export function bloomHashBitOrFactory(token: Type<any>| InjectionToken<any>): number|Function|
|
export function bloomHashBitOrFactory(token: Type<any>| InjectionToken<any>| string): number|
|
||||||
undefined {
|
Function|undefined {
|
||||||
ngDevMode && assertDefined(token, 'token must be defined');
|
ngDevMode && assertDefined(token, 'token must be defined');
|
||||||
|
if (typeof token === 'string') {
|
||||||
|
return token.charCodeAt(0) || 0;
|
||||||
|
}
|
||||||
const tokenId: number|undefined = (token as any)[NG_ELEMENT_ID];
|
const tokenId: number|undefined = (token as any)[NG_ELEMENT_ID];
|
||||||
return typeof tokenId === 'number' ? tokenId & BLOOM_MASK : tokenId;
|
return typeof tokenId === 'number' ? tokenId & BLOOM_MASK : tokenId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition';
|
||||||
|
|
||||||
import {defineComponent} from '../../src/render3/definition';
|
import {defineComponent} from '../../src/render3/definition';
|
||||||
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
|
import {bloomAdd, bloomHasToken, bloomHashBitOrFactory as bloomHash, getOrCreateNodeInjectorForNode} from '../../src/render3/di';
|
||||||
import {defineDirective, elementProperty, load, templateRefExtractor} from '../../src/render3/index';
|
import {ProvidersFeature, defineDirective, elementProperty, load, templateRefExtractor} from '../../src/render3/index';
|
||||||
|
|
||||||
import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLView, createTView, directiveInject, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, injectAttribute, interpolation2, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, createNodeAtIndex, createLView, createTView, directiveInject, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, injectAttribute, interpolation2, projection, projectionDef, reference, template, text, textBinding, elementContainerStart, elementContainerEnd} from '../../src/render3/instructions';
|
||||||
import {isProceduralRenderer, RElement} from '../../src/render3/interfaces/renderer';
|
import {isProceduralRenderer, RElement} from '../../src/render3/interfaces/renderer';
|
||||||
|
@ -1719,6 +1719,36 @@ describe('di', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('string tokens', () => {
|
||||||
|
it('should be able to provide a string token', () => {
|
||||||
|
let injectorDir !: InjectorDir;
|
||||||
|
let divElement !: HTMLElement;
|
||||||
|
|
||||||
|
class InjectorDir {
|
||||||
|
constructor(public value: string) {}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: InjectorDir,
|
||||||
|
selectors: [['', 'injectorDir', '']],
|
||||||
|
factory: () => injectorDir = new InjectorDir(directiveInject('test' as any)),
|
||||||
|
features: [ProvidersFeature([{provide: 'test', useValue: 'provided'}])],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** <div injectorDir otherInjectorDir></div> */
|
||||||
|
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
element(0, 'div', ['injectorDir', '']);
|
||||||
|
}
|
||||||
|
// testing only
|
||||||
|
divElement = load(0);
|
||||||
|
}, 1, 0, [InjectorDir]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(App);
|
||||||
|
expect(injectorDir.value).toBe('provided');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Renderer2', () => {
|
describe('Renderer2', () => {
|
||||||
class MyComp {
|
class MyComp {
|
||||||
constructor(public renderer: Renderer2) {}
|
constructor(public renderer: Renderer2) {}
|
||||||
|
|
Loading…
Reference in New Issue