Skip to content

Commit 6159102

Browse files
committed
fixup! feat(core): support object-based DI flags in TestBed.inject()
1 parent ad411b8 commit 6159102

File tree

8 files changed

+102
-14
lines changed

8 files changed

+102
-14
lines changed

goldens/public-api/core/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,10 @@ export const ENVIRONMENT_INITIALIZER: InjectionToken<() => void>;
492492
export abstract class EnvironmentInjector implements Injector {
493493
// (undocumented)
494494
abstract destroy(): void;
495+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
496+
optional?: false;
497+
}): T;
498+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
495499
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
496500
// @deprecated
497501
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
@@ -733,6 +737,10 @@ export abstract class Injector {
733737
parent?: Injector;
734738
name?: string;
735739
}): Injector;
740+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
741+
optional?: false;
742+
}): T;
743+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
736744
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions | InjectFlags): T;
737745
// @deprecated
738746
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;

goldens/public-api/core/testing/index.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,12 @@ export interface TestBed {
117117
initTestEnvironment(ngModule: Type<any> | Type<any>[], platform: PlatformRef, options?: TestEnvironmentOptions): void;
118118
// (undocumented)
119119
inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions & {
120-
optional: true;
121-
}): T | null;
120+
optional?: false;
121+
}): T;
122122
// (undocumented)
123-
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
123+
inject<T>(token: ProviderToken<T>, notFoundValue: null | undefined, options: InjectOptions): T | null;
124124
// (undocumented)
125-
inject<T>(token: ProviderToken<T>, notFoundValue: null, options?: InjectOptions): T | null;
125+
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
126126
// @deprecated (undocumented)
127127
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
128128
// @deprecated (undocumented)

packages/core/src/di/injector.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ export abstract class Injector {
5151
* It can **not** be done in minor/patch, since it's breaking for custom injectors
5252
* that only implement the old `InjectorFlags` interface.
5353
*/
54+
55+
/**
56+
* Retrieves an instance from the injector based on the provided token.
57+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
58+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
59+
*/
60+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
61+
optional?: false;
62+
}): T;
63+
/**
64+
* Retrieves an instance from the injector based on the provided token.
65+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
66+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
67+
*/
68+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T
69+
|null;
5470
/**
5571
* Retrieves an instance from the injector based on the provided token.
5672
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.

packages/core/src/di/r3_injector.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ interface Record<T> {
7777
* @developerPreview
7878
*/
7979
export abstract class EnvironmentInjector implements Injector {
80+
/**
81+
* Retrieves an instance from the injector based on the provided token.
82+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
83+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
84+
*/
85+
abstract get<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
86+
optional?: false;
87+
}): T;
88+
/**
89+
* Retrieves an instance from the injector based on the provided token.
90+
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
91+
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
92+
*/
93+
abstract get<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T
94+
|null;
8095
/**
8196
* Retrieves an instance from the injector based on the provided token.
8297
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.

packages/core/test/acceptance/di_spec.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {CommonModule} from '@angular/common';
10-
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, createEnvironmentInjector, Directive, ElementRef, ENVIRONMENT_INITIALIZER, EnvironmentInjector, EventEmitter, forwardRef, Host, HostBinding, ImportedNgModuleProviders, importProvidersFrom, ImportProvidersSource, inject, Inject, Injectable, InjectFlags, InjectionToken, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewChild, ViewContainerRef, ViewEncapsulation, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core';
10+
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, createEnvironmentInjector, Directive, ElementRef, ENVIRONMENT_INITIALIZER, EnvironmentInjector, EventEmitter, forwardRef, Host, HostBinding, ImportedNgModuleProviders, importProvidersFrom, ImportProvidersSource, inject, Inject, Injectable, InjectFlags, InjectionToken, InjectOptions, INJECTOR, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Provider, Self, SkipSelf, TemplateRef, Type, ViewChild, ViewContainerRef, ViewEncapsulation, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core';
1111
import {ViewRef as ViewRefInternal} from '@angular/core/src/render3/view_ref';
1212
import {TestBed} from '@angular/core/testing';
1313
import {By} from '@angular/platform-browser';
@@ -3467,6 +3467,39 @@ describe('di', () => {
34673467
expect(envInjector.get(TOKEN, undefined, InjectFlags.Optional)).toBeNull();
34683468
});
34693469

3470+
it('should include `null` into the result type when the optional flag is used', () => {
3471+
const TOKEN = new InjectionToken<string>('TOKEN');
3472+
3473+
@Component({
3474+
standalone: true,
3475+
template: '',
3476+
})
3477+
class TestCmp {
3478+
nodeInjector = inject(Injector);
3479+
envInjector = inject(EnvironmentInjector);
3480+
}
3481+
3482+
const {nodeInjector, envInjector} = TestBed.createComponent(TestCmp).componentInstance;
3483+
3484+
const flags: InjectOptions = {optional: true};
3485+
3486+
let nodeInjectorResult = nodeInjector.get(TOKEN, undefined, flags);
3487+
expect(nodeInjectorResult).toBe(null);
3488+
3489+
// Verify that `null` can be a valid value (from typing standpoint),
3490+
// the line below would fail a type check in case the result doesn't
3491+
// have `null` in the type.
3492+
nodeInjectorResult = null;
3493+
3494+
let envInjectorResult = envInjector.get(TOKEN, undefined, flags);
3495+
expect(envInjectorResult).toBe(null);
3496+
3497+
// Verify that `null` can be a valid value (from typing standpoint),
3498+
// the line below would fail a type check in case the result doesn't
3499+
// have `null` in the type.
3500+
envInjectorResult = null;
3501+
});
3502+
34703503
it('should be able to use skipSelf injection in NodeInjector', () => {
34713504
const TOKEN = new InjectionToken<string>('TOKEN', {
34723505
providedIn: 'root',

packages/core/test/test_bed_spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
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';
9+
import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ElementRef, ErrorHandler, getNgModuleById, Inject, Injectable, InjectFlags, InjectionToken, InjectOptions, 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';
1010
import {TestBed, TestBedImpl} from '@angular/core/testing/src/test_bed';
1111
import {By} from '@angular/platform-browser';
1212
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -1866,6 +1866,19 @@ describe('TestBed', () => {
18661866
expect(TestBed.inject(TOKEN, undefined, InjectFlags.Optional)).toBeNull();
18671867
});
18681868

1869+
it('should include `null` into the result type when the optional flag is used', () => {
1870+
const TOKEN = new InjectionToken<string>('TOKEN');
1871+
1872+
const flags: InjectOptions = {optional: true};
1873+
let result = TestBed.inject(TOKEN, undefined, flags);
1874+
expect(result).toBe(null);
1875+
1876+
// Verify that `null` can be a valid value (from typing standpoint),
1877+
// the line below would fail a type check in case the result doesn't
1878+
// have `null` in the type.
1879+
result = null;
1880+
});
1881+
18691882
it('should be able to use skipSelf injection', () => {
18701883
const TOKEN = new InjectionToken<string>('TOKEN');
18711884
TestBed.configureTestingModule({

packages/core/testing/src/test_bed.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ export interface TestBed {
9090
compileComponents(): Promise<any>;
9191

9292
inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
93-
optional: true
94-
}): T|null;
93+
optional?: false
94+
}): T;
95+
inject<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions): T|null;
9596
inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
96-
inject<T>(token: ProviderToken<T>, notFoundValue: null, options?: InjectOptions): T|null;
9797
/** @deprecated use object-based flags (`InjectOptions`) instead. */
9898
inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
9999
/** @deprecated use object-based flags (`InjectOptions`) instead. */
@@ -299,11 +299,12 @@ export class TestBedImpl implements TestBed {
299299
return TestBedImpl.INSTANCE.overrideProvider(token, provider);
300300
}
301301

302-
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
303-
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions&{
302+
static inject<T>(token: ProviderToken<T>, notFoundValue: undefined, options: InjectOptions&{
304303
optional?: false
305304
}): T;
306-
static inject<T>(token: ProviderToken<T>, notFoundValue: null, options?: InjectOptions): T|null;
305+
static inject<T>(token: ProviderToken<T>, notFoundValue: null|undefined, options: InjectOptions):
306+
T|null;
307+
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, options?: InjectOptions): T;
307308
/** @deprecated use object-based flags (`InjectOptions`) instead. */
308309
static inject<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
309310
/** @deprecated use object-based flags (`InjectOptions`) instead. */

packages/examples/core/di/ts/injector_spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {inject, InjectFlags, InjectionToken, Injector, ProviderToken, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core';
9+
import {inject, InjectFlags, InjectionToken, InjectOptions, Injector, ProviderToken, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core';
1010

1111
class MockRootScopeInjector implements Injector {
1212
constructor(readonly parent: Injector) {}
1313

14-
get<T>(token: ProviderToken<T>, defaultValue?: any, flags: InjectFlags = InjectFlags.Default): T {
14+
get<T>(
15+
token: ProviderToken<T>, defaultValue?: any,
16+
flags: InjectFlags|InjectOptions = InjectFlags.Default): T {
1517
if ((token as any).ɵprov && (token as any).ɵprov.providedIn === 'root') {
1618
const old = setCurrentInjector(this);
1719
try {

0 commit comments

Comments
 (0)