Skip to content

Commit

Permalink
fix(core): handle ChainedInjectors in injector debug utils (#55144)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
AndrewKushnir authored and thePunderWoman committed Apr 3, 2024
1 parent 816ddf4 commit 158ceaf
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 13 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/defer/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,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);
}
Expand Down
24 changes: 13 additions & 11 deletions packages/core/src/render3/util/injector_discovery_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@
* 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';
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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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');
}
Expand Down
17 changes: 16 additions & 1 deletion packages/core/test/acceptance/defer_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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],
Expand All @@ -4171,6 +4177,9 @@ describe('@defer', () => {
})
class AnotherChild {
route = inject(ActivatedRoute);
constructor() {
routeCmpNodeInjector = inject(Injector);
}
}

@Component({
Expand Down Expand Up @@ -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);
});
});
});

0 comments on commit 158ceaf

Please sign in to comment.