Skip to content

Commit

Permalink
fix(core): handle ChainedInjectors in injector debug utils
Browse files Browse the repository at this point in the history
The fix from PR angular#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 angular#55137.
  • Loading branch information
AndrewKushnir committed Apr 1, 2024
1 parent 87f3f27 commit 403004e
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 12 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/defer/instructions.ts
Expand Up @@ -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);
}
Expand Down
22 changes: 12 additions & 10 deletions packages/core/src/render3/util/injector_discovery_utils.ts
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,6 +585,8 @@ 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');
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
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 403004e

Please sign in to comment.