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
|
||||
*/
|
||||
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');
|
||||
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<any>| InjectionToken<any>): number|Function|
|
||||
undefined {
|
||||
export function bloomHashBitOrFactory(token: Type<any>| InjectionToken<any>| 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;
|
||||
}
|
||||
|
|
|
@ -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'}])],
|
||||
});
|
||||
}
|
||||
|
||||
/** <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', () => {
|
||||
class MyComp {
|
||||
constructor(public renderer: Renderer2) {}
|
||||
|
|
Loading…
Reference in New Issue