fix(core): improve injector debug information in `ngDevMode` (#40476)

- `LViewDebug` now properly shows when `TNode` has `NO_NODE_INJECTOR`.
- Provide `injectorResolutionPath` property `DebugNode`

PR Close #40476
This commit is contained in:
Misko Hevery 2021-01-18 18:32:36 -08:00 committed by Jessica Janiuk
parent 07b7af332f
commit 1e4b51e9f7
3 changed files with 98 additions and 7 deletions

View File

@ -15,7 +15,7 @@ import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
import {assertNodeInjector} from '../assert';
import {getInjectorIndex} from '../di';
import {getInjectorIndex, getParentInjectorLocation} from '../di';
import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector';
@ -25,7 +25,7 @@ import {LQueries, TQueries} from '../interfaces/query';
import {Renderer3, RendererFactory3} from '../interfaces/renderer';
import {RComment, RElement, RNode} from '../interfaces/renderer_dom';
import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, NodeInjectorDebug, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view';
import {attachDebugObject} from '../util/debug_utils';
import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils';
import {unwrapRNode} from '../util/view_utils';
@ -216,8 +216,20 @@ class TNode implements ITNode {
debugNodeInjectorPath(lView: LView): DebugNode[] {
const path: DebugNode[] = [];
let injectorIndex = getInjectorIndex(this, lView);
ngDevMode && assertNodeInjector(lView, injectorIndex);
if (injectorIndex === -1) {
// Looks like the current `TNode` does not have `NodeInjector` associated with it => look for
// parent NodeInjector.
const parentLocation = getParentInjectorLocation(this, lView);
if (parentLocation !== NO_PARENT_INJECTOR) {
// We found a parent, so start searching from the parent location.
injectorIndex = getParentInjectorIndex(parentLocation);
lView = getParentInjectorView(parentLocation, lView);
} else {
// No parents have been found, so there are no `NodeInjector`s to consult.
}
}
while (injectorIndex !== -1) {
ngDevMode && assertNodeInjector(lView, injectorIndex);
const tNode = lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] as TNode;
path.push(buildDebugNode(tNode, lView));
const parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT];
@ -583,15 +595,19 @@ export function buildDebugNode(tNode: ITNode, lView: LView): DebugNode {
return {
html: toHtml(native),
type: toTNodeTypeAsString(tNode.type),
tNode,
native: native as any,
children: toDebugNodes(tNode.child, lView),
factories,
instances,
injector: buildNodeInjectorDebug(tNode, tView, lView)
injector: buildNodeInjectorDebug(tNode, tView, lView),
get injectorResolutionPath() {
return (tNode as TNode).debugNodeInjectorPath(lView);
},
};
}
function buildNodeInjectorDebug(tNode: ITNode, tView: ITView, lView: LView) {
function buildNodeInjectorDebug(tNode: ITNode, tView: ITView, lView: LView): NodeInjectorDebug {
const viewProviders: Type<any>[] = [];
for (let i = (tNode as TNode).providerIndexStart_; i < (tNode as TNode).providerIndexEnd_; i++) {
viewProviders.push(tView.data[i] as Type<any>);
@ -633,6 +649,9 @@ function binary(array: any[], idx: number): string {
* @param idx
*/
function toBloom(array: any[], idx: number): string {
if (idx < 0) {
return 'NO_NODE_INJECTOR';
}
return `${binary(array, idx + 7)}_${binary(array, idx + 6)}_${binary(array, idx + 5)}_${
binary(array, idx + 4)}_${binary(array, idx + 3)}_${binary(array, idx + 2)}_${
binary(array, idx + 1)}_${binary(array, idx + 0)}`;

View File

@ -1089,6 +1089,11 @@ export interface DebugNode {
*/
html: string|null;
/**
* Associated `TNode`
*/
tNode: TNode;
/**
* Human readable node type.
*/
@ -1118,6 +1123,11 @@ export interface DebugNode {
* NodeInjector information.
*/
injector: NodeInjectorDebug;
/**
* Injector resolution path.
*/
injectorResolutionPath: any;
}
export interface NodeInjectorDebug {

View File

@ -6,15 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature} from '@angular/core/src/core';
import {Component, Injectable, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature} from '@angular/core/src/core';
import {getLContext} from '@angular/core/src/render3/context_discovery';
import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '@angular/core/src/render3/instructions/element';
import {TNodeDebug} from '@angular/core/src/render3/instructions/lview_debug';
import {createTNode, createTView} from '@angular/core/src/render3/instructions/shared';
import {MONKEY_PATCH_KEY_NAME} from '@angular/core/src/render3/interfaces/context';
import {TNodeType} from '@angular/core/src/render3/interfaces/node';
import {LView, TView, TViewType} from '@angular/core/src/render3/interfaces/view';
import {LView, LViewDebug, TView, TViewType} from '@angular/core/src/render3/interfaces/view';
import {enterView, leaveView} from '@angular/core/src/render3/state';
import {insertTStylingBinding} from '@angular/core/src/render3/styling/style_binding_list';
import {getComponentLView} from '@angular/core/src/render3/util/discovery_utils';
import {KeyValueArray} from '@angular/core/src/util/array_utils';
import {TestBed} from '@angular/core/testing';
import {TemplateFixture} from '../render_util';
describe('lView_debug', () => {
@ -239,4 +243,62 @@ describe('lView_debug', () => {
});
});
});
describe('debugNodeInjectorPath', () => {
@Injectable()
class MyService {
}
@Injectable()
class OtherService {
}
@Component({
selector: 'parent',
template: `Parent: [<child></child>]`,
providers: [MyService],
})
class ParentComponent {
}
@Component({
selector: 'child',
template: `<b>Child!</b>`,
providers: [OtherService],
})
class ChildComponent {
constructor(private myService: MyService, private otherService: OtherService) {}
}
it('should display injection path', () => {
expect(ngDevMode).toBeTruthy();
TestBed.configureTestingModule({declarations: [ParentComponent, ChildComponent]});
const parentFixture = TestBed.createComponent(ParentComponent);
const parentHostElement = parentFixture.nativeElement as HTMLElement;
const childElement = parentHostElement.querySelector('child')! as HTMLElement;
if (!(childElement as any)[MONKEY_PATCH_KEY_NAME]) {
// In these browsers:
// - Chrome Mobile 72.0.3626 (Android 0.0.0)
// - IE 11.0.0 (Windows 8.1.0.0)
// Retrieving `LContext` does not work for unknown reasons, and we are unable to debug it.
// Exiting tests early to prevent breaking the test suite.
return;
}
const childLViewDebug = getComponentLView(childElement).debug!;
const parentLViewDebug = childLViewDebug.parent as LViewDebug;
const rootLViewDebug = parentLViewDebug.parent! as LViewDebug;
const childRootNode = childLViewDebug.nodes[0];
expect(childRootNode.injector.bloom).toEqual('NO_NODE_INJECTOR');
expect(childRootNode.injector.cumulativeBloom).toEqual('NO_NODE_INJECTOR');
const injectorResolutionPath = childRootNode.injectorResolutionPath;
expect(injectorResolutionPath.length).toEqual(2);
expect(injectorResolutionPath[0].injector)
.toEqual(
parentLViewDebug.nodes[1].injector,
);
expect(injectorResolutionPath[1].injector)
.toEqual(
rootLViewDebug.nodes[0].injector,
);
});
});
});