Skip to content

Commit 231e0a3

Browse files
AndrewKushnirthePunderWoman
authored andcommitted
fix(core): handle ChainedInjectors in injector debug utils (#55144)
The fix from PR #55079 introduced a configuration of the injector chain, which wasn't properly handled by the injector debug utils, thus resulting in JS exceptions in DevTools. This commit updates injector debug utils logic that calculates injector resolution path to also handle `ChainedInjector`s. Resolves #55137. PR Close #55144
1 parent f577319 commit 231e0a3

File tree

3 files changed

+30
-13
lines changed

3 files changed

+30
-13
lines changed

packages/core/src/defer/instructions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ export function renderDeferBlockState(
505505
* Detects whether an injector is an instance of a `ChainedInjector`,
506506
* created based on the `OutletInjector`.
507507
*/
508-
function isRouterOutletInjector(currentInjector: Injector): boolean {
508+
export function isRouterOutletInjector(currentInjector: Injector): boolean {
509509
return (currentInjector instanceof ChainedInjector) &&
510510
((currentInjector.injector as any).__ngOutletInjector);
511511
}

packages/core/src/render3/util/injector_discovery_utils.ts

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

9+
import {ENVIRONMENT_INITIALIZER} from '../../di/initializer_token';
910
import {InjectionToken} from '../../di/injection_token';
1011
import {Injector} from '../../di/injector';
1112
import {getInjectorDef, InjectorType} from '../../di/interface/defs';
1213
import {InjectFlags, InternalInjectFlags} from '../../di/interface/injector';
14+
import {ValueProvider} from '../../di/interface/provider';
15+
import {INJECTOR_DEF_TYPES} from '../../di/internal_tokens';
1316
import {NullInjector} from '../../di/null_injector';
1417
import {SingleProvider, walkProviderTree} from '../../di/provider_collection';
1518
import {EnvironmentInjector, R3Injector} from '../../di/r3_injector';
1619
import {Type} from '../../interface/type';
1720
import {NgModuleRef as viewEngine_NgModuleRef} from '../../linker/ng_module_factory';
1821
import {deepForEach} from '../../util/array_utils';
1922
import {assertDefined, throwError} from '../../util/assert';
20-
import type {ChainedInjector} from '../component_ref';
21-
import {getComponentDef} from '../definition';
22-
import {getNodeInjectorLView, getNodeInjectorTNode, getParentInjectorLocation, NodeInjector} from '../di';
23+
import {assertTNode, assertTNodeForLView} from '../assert';
24+
import {ChainedInjector} from '../component_ref';
2325
import {getFrameworkDIDebugData} from '../debug/framework_injector_profiler';
2426
import {InjectedService, ProviderRecord} from '../debug/injector_profiler';
27+
import {getComponentDef} from '../definition';
28+
import {getNodeInjectorLView, getNodeInjectorTNode, getParentInjectorLocation, NodeInjector} from '../di';
2529
import {NodeInjectorOffset} from '../interfaces/injector';
2630
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from '../interfaces/node';
31+
import {RElement} from '../interfaces/renderer_dom';
2732
import {INJECTOR, LView, TVIEW} from '../interfaces/view';
2833

2934
import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './injector_utils';
30-
import {assertTNodeForLView, assertTNode} from '../assert';
31-
import {RElement} from '../interfaces/renderer_dom';
3235
import {getNativeByTNode} from './view_utils';
33-
import {INJECTOR_DEF_TYPES} from '../../di/internal_tokens';
34-
import {ENVIRONMENT_INITIALIZER} from '../../di/initializer_token';
35-
import {ValueProvider} from '../../di/interface/provider';
3636

3737
/**
3838
* Discovers the dependencies of an injectable instance. Provides DI information about each
@@ -585,9 +585,11 @@ function getInjectorParent(injector: Injector): Injector|null {
585585
lView = getNodeInjectorLView(injector);
586586
} else if (injector instanceof NullInjector) {
587587
return null;
588+
} else if (injector instanceof ChainedInjector) {
589+
return injector.parentInjector;
588590
} else {
589591
throwError(
590-
'getInjectorParent only support injectors of type R3Injector, NodeInjector, NullInjector');
592+
'getInjectorParent only support injectors of type R3Injector, NodeInjector, NullInjector, ChainedInjector');
591593
}
592594

593595
const parentLocation = getParentInjectorLocation(
@@ -633,8 +635,8 @@ function getModuleInjectorOfNodeInjector(injector: NodeInjector): Injector {
633635
throwError('getModuleInjectorOfNodeInjector must be called with a NodeInjector');
634636
}
635637

636-
const chainedInjector = lView[INJECTOR] as ChainedInjector;
637-
const moduleInjector = chainedInjector.parentInjector;
638+
const inj = lView[INJECTOR] as R3Injector | ChainedInjector;
639+
const moduleInjector = (inj instanceof ChainedInjector) ? inj.parentInjector : inj.parent;
638640
if (!moduleInjector) {
639641
throwError('NodeInjector must have some connection to the module injector tree');
640642
}

packages/core/test/acceptance/defer_spec.ts

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

99
import {CommonModule, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
10-
import {ApplicationRef, Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, createComponent, DebugElement, Directive, EnvironmentInjector, ErrorHandler, getDebugNode, inject, Injectable, InjectionToken, Input, NgModule, NgZone, Pipe, PipeTransform, PLATFORM_ID, QueryList, Type, ViewChildren, ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR} from '@angular/core';
10+
import {ApplicationRef, Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, createComponent, DebugElement, Directive, EnvironmentInjector, ErrorHandler, getDebugNode, inject, Injectable, InjectionToken, Injector, Input, NgModule, NgZone, Pipe, PipeTransform, PLATFORM_ID, QueryList, Type, ViewChildren, ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR} from '@angular/core';
11+
import {isRouterOutletInjector} from '@angular/core/src/defer/instructions';
12+
import {ChainedInjector} from '@angular/core/src/render3/component_ref';
1113
import {getComponentDef} from '@angular/core/src/render3/definition';
14+
import {NodeInjector} from '@angular/core/src/render3/di';
15+
import {getInjectorResolutionPath} from '@angular/core/src/render3/util/injector_discovery_utils';
1216
import {ComponentFixture, DeferBlockBehavior, fakeAsync, flush, TestBed, tick} from '@angular/core/testing';
1317
import {ActivatedRoute, provideRouter, Router, RouterOutlet} from '@angular/router';
1418

@@ -4155,6 +4159,8 @@ describe('@defer', () => {
41554159

41564160
describe('Router', () => {
41574161
it('should inject correct `ActivatedRoutes` in components within defer blocks', async () => {
4162+
let routeCmpNodeInjector;
4163+
41584164
@Component({
41594165
standalone: true,
41604166
imports: [RouterOutlet],
@@ -4171,6 +4177,9 @@ describe('@defer', () => {
41714177
})
41724178
class AnotherChild {
41734179
route = inject(ActivatedRoute);
4180+
constructor() {
4181+
routeCmpNodeInjector = inject(Injector);
4182+
}
41744183
}
41754184

41764185
@Component({
@@ -4215,6 +4224,12 @@ describe('@defer', () => {
42154224

42164225
expect(app.nativeElement.innerHTML).toContain('child: a');
42174226
expect(app.nativeElement.innerHTML).toContain('another child: a');
4227+
4228+
// Verify that the first non-NodeInjector refers to the chained injector,
4229+
// which represents OutletInjector.
4230+
const path = getInjectorResolutionPath(routeCmpNodeInjector!);
4231+
const firstEnvInjector = path.find(inj => !(inj instanceof NodeInjector))!;
4232+
expect(isRouterOutletInjector(firstEnvInjector)).toBe(true);
42184233
});
42194234
});
42204235
});

0 commit comments

Comments
 (0)