From 64a34616d810e066a541059f70b740959aea0cb8 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 28 Nov 2018 12:51:00 -0800 Subject: [PATCH] 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 --- packages/core/src/render3/di.ts | 12 ++++++---- packages/core/test/render3/di_spec.ts | 32 ++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 0a824a5edd..2fff6fadca 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -87,9 +87,10 @@ let nextNgElementId = 0; * @param type The directive token to register */ export function bloomAdd( - injectorIndex: number, tView: TView, type: Type| InjectionToken): void { + injectorIndex: number, tView: TView, type: Type| InjectionToken| string): void { 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, // 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 * @returns the matching bit to check in the bloom filter or `null` if the token is not known. */ -export function bloomHashBitOrFactory(token: Type| InjectionToken): number|Function| - undefined { +export function bloomHashBitOrFactory(token: Type| InjectionToken| string): number| + Function|undefined { 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]; return typeof tokenId === 'number' ? tokenId & BLOOM_MASK : tokenId; } diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 4eb42e1ff5..51129f7015 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -11,7 +11,7 @@ import {RenderFlags} from '@angular/core/src/render3/interfaces/definition'; import {defineComponent} from '../../src/render3/definition'; 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 {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'}])], + }); + } + + /**
*/ + 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', () => { class MyComp { constructor(public renderer: Renderer2) {}