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:
parent
07b7af332f
commit
1e4b51e9f7
|
@ -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)}`;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue