Skip to content

Commit

Permalink
fix(core): make parent injector argument required in `createEnvironme…
Browse files Browse the repository at this point in the history
…ntInjector` (#46397)

Previously, the `createEnvironmentInjector` function allowed creating an instance of an EnvironmentInjector without providing a parent injector. This resulted in an injector instance, which was detached from the DI tree, thus having limited value. This commit updates the types of the `createEnvironmentInjector` function to make the parent injector a required argument.

PR Close #46397
  • Loading branch information
AndrewKushnir committed Jun 17, 2022
1 parent 82acbf9 commit bb7c804
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 28 deletions.
2 changes: 1 addition & 1 deletion goldens/public-api/core/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export interface ContentChildrenDecorator {
}

// @public
export function createEnvironmentInjector(providers: Array<Provider | ImportedNgModuleProviders>, parent?: EnvironmentInjector | null, debugName?: string | null): EnvironmentInjector;
export function createEnvironmentInjector(providers: Array<Provider | ImportedNgModuleProviders>, parent: EnvironmentInjector, debugName?: string | null): EnvironmentInjector;

// @public
export function createNgModuleRef<T>(ngModule: Type<T>, parentInjector?: Injector): NgModuleRef<T>;
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/render3/ng_module_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,19 @@ class EnvironmentNgModuleRefAdapter extends viewEngine_NgModuleRef<null> {
/**
* Create a new environment injector.
*
* Learn more about environment injectors in
* [this guide](guide/standalone-components#environment-injectors).
*
* @param providers An array of providers.
* @param parent A parent environment injector.
* @param debugName An optional name for this injector instance, which will be used in error
* messages.
*
* @publicApi
* @developerPreview
*/
export function createEnvironmentInjector(
providers: Array<Provider|ImportedNgModuleProviders>, parent: EnvironmentInjector|null = null,
providers: Array<Provider|ImportedNgModuleProviders>, parent: EnvironmentInjector,
debugName: string|null = null): EnvironmentInjector {
const adapter = new EnvironmentNgModuleRefAdapter(providers, parent, debugName);
return adapter.injector;
Expand Down
5 changes: 3 additions & 2 deletions packages/core/test/acceptance/di_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {CommonModule} from '@angular/common';
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, createEnvironmentInjector, Directive, ElementRef, ENVIRONMENT_INITIALIZER, 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, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core';
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, ViewRef, ɵcreateInjector as createInjector, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID, ɵINJECTOR_SCOPE} from '@angular/core';
import {ViewRef as ViewRefInternal} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
Expand Down Expand Up @@ -210,7 +210,8 @@ describe('importProvidersFrom', () => {
expect(hasProviderWithToken(providers, C)).toBe(true);
expect(hasProviderWithToken(providers, D)).toBe(true);

const injector = createEnvironmentInjector(providers);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const injector = createEnvironmentInjector(providers, parentEnvInjector);

// Verify that overridden token A has the right value.
expect(injector.get(A)).toBe('Overridden A');
Expand Down
19 changes: 13 additions & 6 deletions packages/core/test/acceptance/env_injector_standalone_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Component, createEnvironmentInjector, importProvidersFrom, InjectionToken, NgModule} from '@angular/core';
import {Component, createEnvironmentInjector, EnvironmentInjector, importProvidersFrom, InjectionToken, NgModule} from '@angular/core';
import {TestBed} from '@angular/core/testing';

import {internalImportProvidersFrom} from '../../src/di/provider_collection';

Expand All @@ -22,8 +23,9 @@ describe('environment injector and standalone components', () => {
class StandaloneComponent {
}

const envInjector =
createEnvironmentInjector(internalImportProvidersFrom(false, StandaloneComponent));
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector(
internalImportProvidersFrom(false, StandaloneComponent), parentEnvInjector);
expect(envInjector.get(ModuleService)).toBeInstanceOf(ModuleService);
});

Expand All @@ -42,7 +44,9 @@ describe('environment injector and standalone components', () => {
class AppModule {
}

const envInjector = createEnvironmentInjector([importProvidersFrom(AppModule)]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector =
createEnvironmentInjector([importProvidersFrom(AppModule)], parentEnvInjector);
expect(envInjector.get(ModuleService)).toBeInstanceOf(ModuleService);
});

Expand All @@ -68,7 +72,9 @@ describe('environment injector and standalone components', () => {
class AppModule {
}

const envInjector = createEnvironmentInjector([importProvidersFrom(AppModule)]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector =
createEnvironmentInjector([importProvidersFrom(AppModule)], parentEnvInjector);
const services = envInjector.get(ModuleService) as ModuleService[];

expect(services.length).toBe(1);
Expand All @@ -90,7 +96,8 @@ describe('environment injector and standalone components', () => {
]
]
];
const envInjector = createEnvironmentInjector(providers);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector(providers, parentEnvInjector);
expect(envInjector.get(A)).toBe('A');
expect(envInjector.get(B)).toBe('B');
expect(envInjector.get(C)).toBe('C');
Expand Down
42 changes: 28 additions & 14 deletions packages/core/test/acceptance/environment_injector_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

import {Component, ComponentFactoryResolver, createEnvironmentInjector, ENVIRONMENT_INITIALIZER, EnvironmentInjector, InjectionToken, INJECTOR, Injector, NgModuleRef} from '@angular/core';
import {R3Injector} from '@angular/core/src/di/r3_injector';
import {TestBed} from '@angular/core/testing';

describe('environment injector', () => {
it('should create and destroy an environment injector', () => {
class Service {}

let destroyed = false;
const envInjector = createEnvironmentInjector([Service]) as R3Injector;
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector([Service], parentEnvInjector) as R3Injector;
envInjector.onDestroy(() => destroyed = true);

const service = envInjector.get(Service);
Expand All @@ -27,38 +29,45 @@ describe('environment injector', () => {
it('should see providers from a parent EnvInjector', () => {
class Service {}

const envInjector = createEnvironmentInjector([], createEnvironmentInjector([Service]));
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector =
createEnvironmentInjector([], createEnvironmentInjector([Service], parentEnvInjector));
expect(envInjector.get(Service)).toBeInstanceOf(Service);
});

it('should shadow providers from the parent EnvInjector', () => {
const token = new InjectionToken('token');

const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector(
[{provide: token, useValue: 'child'}],
createEnvironmentInjector([{provide: token, useValue: 'parent'}]));
createEnvironmentInjector([{provide: token, useValue: 'parent'}], parentEnvInjector));
expect(envInjector.get(token)).toBe('child');
});

it('should expose the Injector token', () => {
const envInjector = createEnvironmentInjector([]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector([], parentEnvInjector);
expect(envInjector.get(Injector)).toBe(envInjector);
expect(envInjector.get(INJECTOR)).toBe(envInjector);
});

it('should expose the EnvInjector token', () => {
const envInjector = createEnvironmentInjector([]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector([], parentEnvInjector);
expect(envInjector.get(EnvironmentInjector)).toBe(envInjector);
});

it('should expose the same object as both the Injector and EnvInjector token', () => {
const envInjector = createEnvironmentInjector([]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector([], parentEnvInjector);
expect(envInjector.get(Injector)).toBe(envInjector.get(EnvironmentInjector));
});

it('should expose the NgModuleRef token', () => {
class Service {}
const envInjector = createEnvironmentInjector([Service]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector([Service], parentEnvInjector);

const ngModuleRef = envInjector.get(NgModuleRef);

Expand All @@ -78,7 +87,8 @@ describe('environment injector', () => {
constructor(readonly service: Service) {}
}

const envInjector = createEnvironmentInjector([Service]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const envInjector = createEnvironmentInjector([Service], parentEnvInjector);
const cfr = envInjector.get(ComponentFactoryResolver);
const cf = cfr.resolveComponentFactory(TestComponent);
const cRef = cf.create(Injector.NULL);
Expand All @@ -88,17 +98,21 @@ describe('environment injector', () => {

it('should support the ENVIRONMENT_INITIALIZER muli-token', () => {
let initialized = false;
createEnvironmentInjector([{
provide: ENVIRONMENT_INITIALIZER,
useValue: () => initialized = true,
multi: true,
}]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
createEnvironmentInjector(
[{
provide: ENVIRONMENT_INITIALIZER,
useValue: () => initialized = true,
multi: true,
}],
parentEnvInjector);

expect(initialized).toBeTrue();
});

it('should adopt environment-scoped providers', () => {
const injector = createEnvironmentInjector([]);
const parentEnvInjector = TestBed.inject(EnvironmentInjector);
const injector = createEnvironmentInjector([], parentEnvInjector);
const EnvScopedToken = new InjectionToken('env-scoped token', {
providedIn: 'environment' as any,
factory: () => true,
Expand Down
8 changes: 4 additions & 4 deletions packages/core/test/acceptance/standalone_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ describe('standalone components, directives and pipes', () => {
parent: this.inj,
});

const rhsInj =
createEnvironmentInjector([{provide: Service, useClass: EnvOverrideService}]);
const rhsInj = createEnvironmentInjector(
[{provide: Service, useClass: EnvOverrideService}], this.envInj);

this.vcRef.createComponent(InnerCmp, {injector: lhsInj, environmentInjector: rhsInj});
}
Expand Down Expand Up @@ -352,8 +352,8 @@ describe('standalone components, directives and pipes', () => {
constructor(readonly envInj: EnvironmentInjector) {}

ngOnInit(): void {
const rhsInj =
createEnvironmentInjector([{provide: Service, useClass: EnvOverrideService}]);
const rhsInj = createEnvironmentInjector(
[{provide: Service, useClass: EnvOverrideService}], this.envInj);

this.vcRef.createComponent(InnerCmp, {environmentInjector: rhsInj});
}
Expand Down

0 comments on commit bb7c804

Please sign in to comment.