From 231e0a3528e152cb1de3a215d437f5104191445c Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Sun, 31 Mar 2024 22:03:07 -0700 Subject: [PATCH] fix(core): handle `ChainedInjector`s 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 --- packages/core/src/defer/instructions.ts | 2 +- .../render3/util/injector_discovery_utils.ts | 24 ++++++++++--------- packages/core/test/acceptance/defer_spec.ts | 17 ++++++++++++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/core/src/defer/instructions.ts b/packages/core/src/defer/instructions.ts index 4b658129d8f94..fb990432670d1 100644 --- a/packages/core/src/defer/instructions.ts +++ b/packages/core/src/defer/instructions.ts @@ -505,7 +505,7 @@ export function renderDeferBlockState( * Detects whether an injector is an instance of a `ChainedInjector`, * created based on the `OutletInjector`. */ -function isRouterOutletInjector(currentInjector: Injector): boolean { +export function isRouterOutletInjector(currentInjector: Injector): boolean { return (currentInjector instanceof ChainedInjector) && ((currentInjector.injector as any).__ngOutletInjector); } diff --git a/packages/core/src/render3/util/injector_discovery_utils.ts b/packages/core/src/render3/util/injector_discovery_utils.ts index fc939f0b57b33..136937dd28a2b 100644 --- a/packages/core/src/render3/util/injector_discovery_utils.ts +++ b/packages/core/src/render3/util/injector_discovery_utils.ts @@ -6,10 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ +import {ENVIRONMENT_INITIALIZER} from '../../di/initializer_token'; import {InjectionToken} from '../../di/injection_token'; import {Injector} from '../../di/injector'; import {getInjectorDef, InjectorType} from '../../di/interface/defs'; import {InjectFlags, InternalInjectFlags} from '../../di/interface/injector'; +import {ValueProvider} from '../../di/interface/provider'; +import {INJECTOR_DEF_TYPES} from '../../di/internal_tokens'; import {NullInjector} from '../../di/null_injector'; import {SingleProvider, walkProviderTree} from '../../di/provider_collection'; import {EnvironmentInjector, R3Injector} from '../../di/r3_injector'; @@ -17,22 +20,19 @@ import {Type} from '../../interface/type'; import {NgModuleRef as viewEngine_NgModuleRef} from '../../linker/ng_module_factory'; import {deepForEach} from '../../util/array_utils'; import {assertDefined, throwError} from '../../util/assert'; -import type {ChainedInjector} from '../component_ref'; -import {getComponentDef} from '../definition'; -import {getNodeInjectorLView, getNodeInjectorTNode, getParentInjectorLocation, NodeInjector} from '../di'; +import {assertTNode, assertTNodeForLView} from '../assert'; +import {ChainedInjector} from '../component_ref'; import {getFrameworkDIDebugData} from '../debug/framework_injector_profiler'; import {InjectedService, ProviderRecord} from '../debug/injector_profiler'; +import {getComponentDef} from '../definition'; +import {getNodeInjectorLView, getNodeInjectorTNode, getParentInjectorLocation, NodeInjector} from '../di'; import {NodeInjectorOffset} from '../interfaces/injector'; import {TContainerNode, TElementContainerNode, TElementNode, TNode} from '../interfaces/node'; +import {RElement} from '../interfaces/renderer_dom'; import {INJECTOR, LView, TVIEW} from '../interfaces/view'; import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './injector_utils'; -import {assertTNodeForLView, assertTNode} from '../assert'; -import {RElement} from '../interfaces/renderer_dom'; import {getNativeByTNode} from './view_utils'; -import {INJECTOR_DEF_TYPES} from '../../di/internal_tokens'; -import {ENVIRONMENT_INITIALIZER} from '../../di/initializer_token'; -import {ValueProvider} from '../../di/interface/provider'; /** * Discovers the dependencies of an injectable instance. Provides DI information about each @@ -585,9 +585,11 @@ function getInjectorParent(injector: Injector): Injector|null { lView = getNodeInjectorLView(injector); } else if (injector instanceof NullInjector) { return null; + } else if (injector instanceof ChainedInjector) { + return injector.parentInjector; } else { throwError( - 'getInjectorParent only support injectors of type R3Injector, NodeInjector, NullInjector'); + 'getInjectorParent only support injectors of type R3Injector, NodeInjector, NullInjector, ChainedInjector'); } const parentLocation = getParentInjectorLocation( @@ -633,8 +635,8 @@ function getModuleInjectorOfNodeInjector(injector: NodeInjector): Injector { throwError('getModuleInjectorOfNodeInjector must be called with a NodeInjector'); } - const chainedInjector = lView[INJECTOR] as ChainedInjector; - const moduleInjector = chainedInjector.parentInjector; + const inj = lView[INJECTOR] as R3Injector | ChainedInjector; + const moduleInjector = (inj instanceof ChainedInjector) ? inj.parentInjector : inj.parent; if (!moduleInjector) { throwError('NodeInjector must have some connection to the module injector tree'); } diff --git a/packages/core/test/acceptance/defer_spec.ts b/packages/core/test/acceptance/defer_spec.ts index b856bdb73532e..1496a2fabb1d9 100644 --- a/packages/core/test/acceptance/defer_spec.ts +++ b/packages/core/test/acceptance/defer_spec.ts @@ -7,8 +7,12 @@ */ import {CommonModule, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common'; -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'; +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'; +import {isRouterOutletInjector} from '@angular/core/src/defer/instructions'; +import {ChainedInjector} from '@angular/core/src/render3/component_ref'; import {getComponentDef} from '@angular/core/src/render3/definition'; +import {NodeInjector} from '@angular/core/src/render3/di'; +import {getInjectorResolutionPath} from '@angular/core/src/render3/util/injector_discovery_utils'; import {ComponentFixture, DeferBlockBehavior, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; import {ActivatedRoute, provideRouter, Router, RouterOutlet} from '@angular/router'; @@ -4155,6 +4159,8 @@ describe('@defer', () => { describe('Router', () => { it('should inject correct `ActivatedRoutes` in components within defer blocks', async () => { + let routeCmpNodeInjector; + @Component({ standalone: true, imports: [RouterOutlet], @@ -4171,6 +4177,9 @@ describe('@defer', () => { }) class AnotherChild { route = inject(ActivatedRoute); + constructor() { + routeCmpNodeInjector = inject(Injector); + } } @Component({ @@ -4215,6 +4224,12 @@ describe('@defer', () => { expect(app.nativeElement.innerHTML).toContain('child: a'); expect(app.nativeElement.innerHTML).toContain('another child: a'); + + // Verify that the first non-NodeInjector refers to the chained injector, + // which represents OutletInjector. + const path = getInjectorResolutionPath(routeCmpNodeInjector!); + const firstEnvInjector = path.find(inj => !(inj instanceof NodeInjector))!; + expect(isRouterOutletInjector(firstEnvInjector)).toBe(true); }); }); });