diff --git a/aio/tests/e2e/src/api-pages.e2e-spec.ts b/aio/tests/e2e/src/api-pages.e2e-spec.ts index f6112b1ef4b54b..b9ba03d385d51c 100644 --- a/aio/tests/e2e/src/api-pages.e2e-spec.ts +++ b/aio/tests/e2e/src/api-pages.e2e-spec.ts @@ -76,11 +76,11 @@ describe('Api pages', () => { it('should show all overloads of interface methods', async () => { await page.navigateTo('api/core/testing/TestBed'); - expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(2); + expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(4); }); it('should show all overloads of pseudo-class methods', async () => { await page.navigateTo('api/core/testing/TestBed'); - expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(2); + expect(await (await page.getInstanceMethodOverloads('inject')).length).toEqual(4); }); }); diff --git a/goldens/public-api/core/testing/index.md b/goldens/public-api/core/testing/index.md index 64423af8c92c39..813e54b80bd148 100644 --- a/goldens/public-api/core/testing/index.md +++ b/goldens/public-api/core/testing/index.md @@ -12,6 +12,7 @@ import { Directive } from '@angular/core'; import { ElementRef } from '@angular/core'; import { InjectFlags } from '@angular/core'; import { InjectionToken } from '@angular/core'; +import { InjectOptions } from '@angular/core'; import { NgModule } from '@angular/core'; import { NgZone } from '@angular/core'; import { Pipe } from '@angular/core'; @@ -115,8 +116,16 @@ export interface TestBed { get(token: any, notFoundValue?: any): any; initTestEnvironment(ngModule: Type | Type[], platform: PlatformRef, options?: TestEnvironmentOptions): void; // (undocumented) - inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; + inject(token: ProviderToken, notFoundValue: undefined, options: InjectOptions & { + optional: true; + }): T | null; + // (undocumented) + inject(token: ProviderToken, notFoundValue?: T, options?: InjectOptions): T; // (undocumented) + inject(token: ProviderToken, notFoundValue: null, options?: InjectOptions): T | null; + // @deprecated (undocumented) + inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; + // @deprecated (undocumented) inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T | null; // (undocumented) get ngModule(): Type | Type[]; diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 790ea6ea9eb924..e63093538efae4 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -12,7 +12,7 @@ export {defaultIterableDiffers as ɵdefaultIterableDiffers, defaultKeyValueDiffe export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants'; export {Console as ɵConsole} from './console'; export {getDebugNodeR2 as ɵgetDebugNodeR2} from './debug/debug_node'; -export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility'; +export {convertToBitFlags as ɵconvertToBitFlags, setCurrentInjector as ɵsetCurrentInjector} from './di/injector_compatibility'; export {getInjectableDef as ɵgetInjectableDef, ɵɵInjectableDeclaration, ɵɵInjectorDef} from './di/interface/defs'; export {INJECTOR_SCOPE as ɵINJECTOR_SCOPE} from './di/scope'; export {formatRuntimeError as ɵformatRuntimeError, RuntimeError as ɵRuntimeError} from './errors'; diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index dc73739dff342f..85de1e1a244fe2 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -45,6 +45,12 @@ export abstract class Injector { static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND; static NULL: Injector = (/* @__PURE__ */ new NullInjector()); + /** + * Internal note on the `options?: InjectOptions|InjectFlags` override of the `get` + * method: consider dropping the `InjectFlags` part in one of the major versions. + * It can **not** be done in minor/patch, since it's breaking for custom injectors + * that only implement the old `InjectorFlags` interface. + */ /** * Retrieves an instance from the injector based on the provided token. * @returns The instance from the injector if defined, otherwise the `notFoundValue`. diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index c5747e747d6fb0..8d8ac811973906 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -650,6 +650,9 @@ { "name": "containsElement" }, + { + "name": "convertToBitFlags" + }, { "name": "convertToMap" }, diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 5cd321e96eabbc..18f160e8c369ca 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -458,6 +458,9 @@ { "name": "connectableObservableDescriptor" }, + { + "name": "convertToBitFlags" + }, { "name": "createElementNode" }, diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 8c9641f301786e..72fd3dc470ac01 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -677,6 +677,9 @@ { "name": "controlPath" }, + { + "name": "convertToBitFlags" + }, { "name": "createDirectivesInstances" }, diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index c413515d3f8e53..7ae87c4cd4202a 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -647,6 +647,9 @@ { "name": "controlPath" }, + { + "name": "convertToBitFlags" + }, { "name": "createDirectivesInstances" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index e2a9d420a17133..0ba74b50d2d27a 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -323,6 +323,9 @@ { "name": "connectableObservableDescriptor" }, + { + "name": "convertToBitFlags" + }, { "name": "createElementRef" }, diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index a2e007400dc19f..e18fab9c6a5b18 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -809,6 +809,9 @@ { "name": "containsTree" }, + { + "name": "convertToBitFlags" + }, { "name": "convertToParamMap" }, diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index f95ecdb3ebfeed..08a93313d486e7 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -392,6 +392,9 @@ { "name": "connectableObservableDescriptor" }, + { + "name": "convertToBitFlags" + }, { "name": "createElementRef" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 413ecb6af3b305..2f3c3847156c36 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -557,6 +557,9 @@ { "name": "connectableObservableDescriptor" }, + { + "name": "convertToBitFlags" + }, { "name": "createDirectivesInstances" }, diff --git a/packages/core/test/test_bed_spec.ts b/packages/core/test/test_bed_spec.ts index fe948c65b47cbe..917698d9183625 100644 --- a/packages/core/test/test_bed_spec.ts +++ b/packages/core/test/test_bed_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ElementRef, ErrorHandler, getNgModuleById, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Pipe, Type, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelementEnd as elementEnd, ɵɵelementStart as elementStart, ɵɵsetNgModuleScope as setNgModuleScope, ɵɵtext as text} from '@angular/core'; +import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ElementRef, ErrorHandler, getNgModuleById, Inject, Injectable, InjectFlags, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Pipe, Type, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelementEnd as elementEnd, ɵɵelementStart as elementStart, ɵɵsetNgModuleScope as setNgModuleScope, ɵɵtext as text} from '@angular/core'; import {TestBed, TestBedImpl} from '@angular/core/testing/src/test_bed'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -1853,6 +1853,33 @@ describe('TestBed', () => { fixture.detectChanges(); expect(fixture!.nativeElement.textContent).toContain('changed'); }); + + describe('TestBed.inject', () => { + describe('injection flags', () => { + it('should be able to optionally inject a token', () => { + const TOKEN = new InjectionToken('TOKEN'); + + expect(TestBed.inject(TOKEN, undefined, {optional: true})).toBeNull(); + expect(TestBed.inject(TOKEN, undefined, InjectFlags.Optional)).toBeNull(); + + expect(TestBed.inject(TOKEN, undefined, {optional: true})).toBeNull(); + expect(TestBed.inject(TOKEN, undefined, InjectFlags.Optional)).toBeNull(); + }); + + it('should be able to use skipSelf injection', () => { + const TOKEN = new InjectionToken('TOKEN'); + TestBed.configureTestingModule({ + providers: [{provide: TOKEN, useValue: 'from TestBed'}], + }); + + expect(TestBed.inject(TOKEN)).toBe('from TestBed'); + + expect(TestBed.inject(TOKEN, undefined, {skipSelf: true, optional: true})).toBeNull(); + expect(TestBed.inject(TOKEN, undefined, InjectFlags.SkipSelf | InjectFlags.Optional)) + .toBeNull(); + }); + }); + }); }); diff --git a/packages/core/testing/src/test_bed.ts b/packages/core/testing/src/test_bed.ts index 47f6197e11241e..aef89fa684a858 100644 --- a/packages/core/testing/src/test_bed.ts +++ b/packages/core/testing/src/test_bed.ts @@ -16,6 +16,7 @@ import { Directive, InjectFlags, InjectionToken, + InjectOptions, Injector, NgModule, NgZone, @@ -23,6 +24,7 @@ import { PlatformRef, ProviderToken, Type, + ɵconvertToBitFlags as convertToBitFlags, ɵflushModuleScopingQueueAsMuchAsPossible as flushModuleScopingQueueAsMuchAsPossible, ɵgetUnknownElementStrictMode as getUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode as getUnknownPropertyStrictMode, @@ -87,7 +89,14 @@ export interface TestBed { compileComponents(): Promise; + inject(token: ProviderToken, notFoundValue: undefined, options: InjectOptions&{ + optional: true + }): T|null; + inject(token: ProviderToken, notFoundValue?: T, options?: InjectOptions): T; + inject(token: ProviderToken, notFoundValue: null, options?: InjectOptions): T|null; + /** @deprecated use object-based flags (`InjectOptions`) instead. */ inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; + /** @deprecated use object-based flags (`InjectOptions`) instead. */ inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T|null; /** @deprecated from v9.0.0 use TestBed.inject */ @@ -290,10 +299,18 @@ export class TestBedImpl implements TestBed { return TestBedImpl.INSTANCE.overrideProvider(token, provider); } + static inject(token: ProviderToken, notFoundValue?: T, options?: InjectOptions): T; + static inject(token: ProviderToken, notFoundValue?: T, options?: InjectOptions&{ + optional?: false + }): T; + static inject(token: ProviderToken, notFoundValue: null, options?: InjectOptions): T|null; + /** @deprecated use object-based flags (`InjectOptions`) instead. */ static inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; + /** @deprecated use object-based flags (`InjectOptions`) instead. */ static inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T|null; - static inject(token: ProviderToken, notFoundValue?: T|null, flags?: InjectFlags): T|null { - return TestBedImpl.INSTANCE.inject(token, notFoundValue, flags); + static inject( + token: ProviderToken, notFoundValue?: T|null, flags?: InjectFlags|InjectOptions): T|null { + return TestBedImpl.INSTANCE.inject(token, notFoundValue, convertToBitFlags(flags)); } /** @deprecated from v9.0.0 use TestBed.inject */ @@ -468,14 +485,22 @@ export class TestBedImpl implements TestBed { return this.compiler.compileComponents(); } + inject(token: ProviderToken, notFoundValue: undefined, options: InjectOptions&{ + optional: true + }): T|null; + inject(token: ProviderToken, notFoundValue?: T, options?: InjectOptions): T; + inject(token: ProviderToken, notFoundValue: null, options?: InjectOptions): T|null; + /** @deprecated use object-based flags (`InjectOptions`) instead. */ inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; + /** @deprecated use object-based flags (`InjectOptions`) instead. */ inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T|null; - inject(token: ProviderToken, notFoundValue?: T|null, flags?: InjectFlags): T|null { + inject(token: ProviderToken, notFoundValue?: T|null, flags?: InjectFlags|InjectOptions): T + |null { if (token as unknown === TestBed) { return this as any; } const UNDEFINED = {} as unknown as T; - const result = this.testModuleRef.injector.get(token, UNDEFINED, flags); + const result = this.testModuleRef.injector.get(token, UNDEFINED, convertToBitFlags(flags)); return result === UNDEFINED ? this.compiler.injector.get(token, notFoundValue, flags) as any : result; }