mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(devtools): add static nodes for html-only block content in the component tree
Add `<html_content>` static nodes to the component tree for control flow blocks that render only HTML content.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
module.exports = {
|
||||
defaultCommandTimeout: 10000, // Increase the default command timeout to 10 seconds
|
||||
viewportHeight: 2160,
|
||||
e2e: {
|
||||
specPattern: 'integration/*.e2e.js',
|
||||
supportFile: 'support/index.js',
|
||||
|
||||
@@ -442,6 +442,7 @@ const prepareForestForSerialization = (
|
||||
children: prepareForestForSerialization(node.children, includeResolutionPath),
|
||||
hydration: node.hydration,
|
||||
controlFlowBlock: node.controlFlowBlock,
|
||||
static: node.static,
|
||||
changeDetection: node.component ? getDirectiveCdStrategy(node.component) : undefined,
|
||||
|
||||
// native elements are not serializable
|
||||
|
||||
@@ -83,6 +83,7 @@ export function createControlFlowTreeNode(
|
||||
tagName: ELEMENT_NAME_MAP[controlFlowBlock.type],
|
||||
nativeElement: undefined,
|
||||
controlFlowBlock: mapToDevtoolsControlFlowModel(controlFlowBlock, iteratorCurrentIdx, rootId),
|
||||
static: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,8 @@ const indexTree = <T extends DevToolsNode<DirectiveInstanceType, ComponentInstan
|
||||
hydration: node.hydration,
|
||||
controlFlowBlock: node.controlFlowBlock,
|
||||
injector: node.injector,
|
||||
} as IndexedNode;
|
||||
static: node.static,
|
||||
} satisfies IndexedNode;
|
||||
};
|
||||
|
||||
export const indexForest = <T extends DevToolsNode<DirectiveInstanceType, ComponentInstanceType>>(
|
||||
|
||||
@@ -95,6 +95,7 @@ export class LTreeStrategy {
|
||||
directives: [],
|
||||
component: null,
|
||||
controlFlowBlock: null, // neither there will be any control flow block
|
||||
static: false,
|
||||
};
|
||||
}
|
||||
for (let i = tNode.directiveStart; i < tNode.directiveEnd; i++) {
|
||||
@@ -120,6 +121,7 @@ export class LTreeStrategy {
|
||||
directives,
|
||||
component,
|
||||
controlFlowBlock: null, // neither there will be any control flow block
|
||||
static: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -192,4 +192,50 @@ describe('render tree extraction', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should add an `<html_content>` static node for a control flow block with HTML-only content', () => {
|
||||
// Represent:
|
||||
//
|
||||
// <app>
|
||||
// @for (...) {
|
||||
// <div>plain HTML, no component/directive</div>
|
||||
// }
|
||||
// </app>
|
||||
|
||||
const appNode = document.createElement('app');
|
||||
const forHostNode = document.createElement('comment');
|
||||
const htmlContentNode = document.createElement('div');
|
||||
|
||||
appNode.appendChild(forHostNode);
|
||||
appNode.appendChild(htmlContentNode);
|
||||
|
||||
componentMap.set(appNode, {});
|
||||
|
||||
controlFlowBlocksMap.set(appNode, [
|
||||
{
|
||||
type: ControlFlowBlockType.For,
|
||||
hostNode: forHostNode,
|
||||
rootNodes: [htmlContentNode],
|
||||
items: [],
|
||||
},
|
||||
]);
|
||||
|
||||
const rtree = treeStrategy.build(appNode);
|
||||
|
||||
expect(rtree[0].children.length).toBe(1);
|
||||
|
||||
const deferNode = rtree[0].children[0];
|
||||
|
||||
expect(deferNode.tagName).toBe('@for');
|
||||
expect(deferNode.children.length).toBe(1);
|
||||
expect(deferNode.children[0]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
tagName: '<html_content>',
|
||||
static: true,
|
||||
controlFlowBlock: null,
|
||||
component: null,
|
||||
children: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,6 +61,7 @@ function extractViewTree(
|
||||
tagName: domNode.nodeName.toLowerCase(),
|
||||
nativeElement: domNode,
|
||||
hydration: hydrationStatus(domNode),
|
||||
static: false,
|
||||
controlFlowBlock: null,
|
||||
};
|
||||
|
||||
@@ -88,9 +89,9 @@ function extractViewTree(
|
||||
|
||||
const childrenResult = isDisplayableNode ? componentTreeNode.children : result;
|
||||
|
||||
for (const node of domNode.childNodes) {
|
||||
if (!nodesToSkip.has(node)) {
|
||||
extractViewTree(node, childrenResult, ctx, nodesToSkip);
|
||||
for (const child of domNode.childNodes) {
|
||||
if (!nodesToSkip.has(child)) {
|
||||
extractViewTree(child, childrenResult, ctx, nodesToSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +111,8 @@ function groupControlFlowBlocksChildren(
|
||||
}
|
||||
|
||||
ctx.blocksIterator.advance();
|
||||
// It's important to store the here index before the recursive call.
|
||||
// It's important to store the current index before the recursive call
|
||||
// because it might change at the time of the passing to `createControlFlowTreeNode`.
|
||||
const iteratorCurrentIdx = ctx.blocksIterator.currentIndex;
|
||||
|
||||
const childrenTree: ComponentTreeNode[] = [];
|
||||
@@ -121,6 +123,20 @@ function groupControlFlowBlocksChildren(
|
||||
}
|
||||
}
|
||||
|
||||
// If the there isn't a children tree (i.e. child components)
|
||||
// but the block has root nodes, we create a static node that
|
||||
// informs the user that the control flow block
|
||||
// has HTML-only content.
|
||||
if (!childrenTree.length && currentBlock.rootNodes.length) {
|
||||
childrenTree.push({
|
||||
tagName: '<html_content>',
|
||||
static: true,
|
||||
controlFlowBlock: null,
|
||||
component: null,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
|
||||
const blockTreeNode = createControlFlowTreeNode(
|
||||
currentBlock,
|
||||
childrenTree,
|
||||
|
||||
+15
@@ -21,6 +21,7 @@ const tree1: DevToolsNode = {
|
||||
],
|
||||
component: null,
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
|
||||
children: [
|
||||
{
|
||||
@@ -34,6 +35,7 @@ const tree1: DevToolsNode = {
|
||||
tagName: 'bar',
|
||||
nativeElement: document.createElement('bar'),
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
},
|
||||
],
|
||||
nativeElement: document.createElement('foo'),
|
||||
@@ -49,6 +51,7 @@ const tree2: DevToolsNode = {
|
||||
],
|
||||
component: null,
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
|
||||
children: [
|
||||
{
|
||||
@@ -62,6 +65,7 @@ const tree2: DevToolsNode = {
|
||||
tagName: 'bar',
|
||||
nativeElement: document.createElement('bar'),
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
@@ -73,6 +77,7 @@ const tree2: DevToolsNode = {
|
||||
directives: [],
|
||||
tagName: 'qux',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
},
|
||||
],
|
||||
nativeElement: document.createElement('foo'),
|
||||
@@ -88,6 +93,8 @@ const tree3: DevToolsNode = {
|
||||
],
|
||||
component: null,
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
|
||||
children: [
|
||||
{
|
||||
children: [],
|
||||
@@ -99,6 +106,7 @@ const tree3: DevToolsNode = {
|
||||
directives: [],
|
||||
tagName: '#comment',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
nativeElement: document.createComment('bar'),
|
||||
},
|
||||
{
|
||||
@@ -111,6 +119,7 @@ const tree3: DevToolsNode = {
|
||||
directives: [],
|
||||
tagName: '#comment',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
nativeElement: document.createComment('bar'),
|
||||
},
|
||||
],
|
||||
@@ -120,6 +129,7 @@ const tree3: DevToolsNode = {
|
||||
const tree4: DevToolsNode = {
|
||||
tagName: 'app',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
directives: [
|
||||
{
|
||||
id: 1,
|
||||
@@ -146,6 +156,7 @@ const tree4: DevToolsNode = {
|
||||
directives: [],
|
||||
tagName: 'bar',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
nativeElement: document.createComment('bar'),
|
||||
},
|
||||
],
|
||||
@@ -157,6 +168,7 @@ const tree4: DevToolsNode = {
|
||||
directives: [],
|
||||
tagName: '#comment',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
nativeElement: document.createComment('bar'),
|
||||
},
|
||||
],
|
||||
@@ -168,6 +180,7 @@ const tree4: DevToolsNode = {
|
||||
directives: [],
|
||||
tagName: '#comment',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
nativeElement: document.createComment('bar'),
|
||||
},
|
||||
],
|
||||
@@ -179,6 +192,7 @@ const tree4: DevToolsNode = {
|
||||
directives: [],
|
||||
tagName: '#comment',
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
nativeElement: document.createComment('bar'),
|
||||
},
|
||||
],
|
||||
@@ -191,6 +205,7 @@ const tree4: DevToolsNode = {
|
||||
tagName: '#comment',
|
||||
nativeElement: document.createComment('bar'),
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
},
|
||||
],
|
||||
nativeElement: document.createElement('foo'),
|
||||
|
||||
+4
@@ -35,6 +35,8 @@ export interface FlatNode {
|
||||
hydration?: HydrationStatus;
|
||||
controlFlowBlock: ControlFlowBlock | null;
|
||||
changeDetection?: ChangeDetection;
|
||||
collapsedByDefault: boolean;
|
||||
static: boolean;
|
||||
hasNativeElement: boolean;
|
||||
}
|
||||
|
||||
@@ -110,8 +112,10 @@ export class ComponentDataSource extends DataSource<FlatNode> {
|
||||
level,
|
||||
hydration: node.hydration,
|
||||
controlFlowBlock: node.controlFlowBlock,
|
||||
static: node.static,
|
||||
changeDetection: node.changeDetection,
|
||||
hasNativeElement: node.hasNativeElement,
|
||||
collapsedByDefault: node.children.every((n) => n.static),
|
||||
};
|
||||
this._nodeToFlat.set(node, flatNode);
|
||||
return flatNode;
|
||||
|
||||
+3
@@ -35,9 +35,12 @@ describe('directive-forest-utils', () => {
|
||||
directives,
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
static: false,
|
||||
},
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
collapsedByDefault: false,
|
||||
static: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+17
-3
@@ -188,6 +188,10 @@ export class DirectiveForestComponent {
|
||||
}
|
||||
|
||||
select(node: FlatNode): void {
|
||||
if (node.static) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.populateParents(node.position);
|
||||
this.selectNode.emit(node.original);
|
||||
this.selectedNode.set(node);
|
||||
@@ -347,13 +351,23 @@ export class DirectiveForestComponent {
|
||||
this.forestRoot = this.dataSource.data[0];
|
||||
|
||||
if (!this.initialized && forest && forest.length) {
|
||||
this.treeControl.expandAll();
|
||||
for (const n of this.treeControl.dataNodes) {
|
||||
if (!n.collapsedByDefault) {
|
||||
this.treeControl.expand(n);
|
||||
} else {
|
||||
this.treeControl.collapse(n);
|
||||
}
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
result.newItems.forEach((item) => (item.newItem = false));
|
||||
}
|
||||
// We want to expand them once they are rendered.
|
||||
// We want to expand them once they are rendered unless
|
||||
// they are `collapsedByDefault`.
|
||||
result.newItems.forEach((item) => {
|
||||
this.treeControl.expand(item);
|
||||
if (!item.collapsedByDefault) {
|
||||
this.treeControl.expand(item);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
+12
-1
@@ -43,6 +43,7 @@ describe('indexForest', () => {
|
||||
changeDetection: 'ng-on-push',
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
static: false,
|
||||
} as DevToolsNode & {hasNativeElement?: boolean},
|
||||
{
|
||||
tagName: 'Child1_2',
|
||||
@@ -56,11 +57,13 @@ describe('indexForest', () => {
|
||||
changeDetection: 'ng-on-push',
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
static: false,
|
||||
} as DevToolsNode & {hasNativeElement?: boolean},
|
||||
],
|
||||
changeDetection: 'ng-on-push',
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
tagName: 'Parent2',
|
||||
@@ -80,6 +83,7 @@ describe('indexForest', () => {
|
||||
changeDetection: 'ng-eager',
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
static: false,
|
||||
} as DevToolsNode & {hasNativeElement?: boolean},
|
||||
{
|
||||
tagName: 'Child2_2',
|
||||
@@ -98,11 +102,13 @@ describe('indexForest', () => {
|
||||
changeDetection: 'ng-eager',
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
static: false,
|
||||
} as DevToolsNode & {hasNativeElement?: boolean},
|
||||
],
|
||||
changeDetection: 'ng-eager',
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
static: false,
|
||||
},
|
||||
]),
|
||||
).toEqual([
|
||||
@@ -137,6 +143,7 @@ describe('indexForest', () => {
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
injector: undefined,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
tagName: 'Child1_2',
|
||||
@@ -151,15 +158,16 @@ describe('indexForest', () => {
|
||||
children: [],
|
||||
controlFlowBlock: null,
|
||||
changeDetection: 'ng-on-push',
|
||||
|
||||
hasNativeElement: true,
|
||||
injector: undefined,
|
||||
static: false,
|
||||
},
|
||||
],
|
||||
controlFlowBlock: null,
|
||||
changeDetection: 'ng-on-push',
|
||||
hasNativeElement: true,
|
||||
injector: undefined,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
tagName: 'Parent2',
|
||||
@@ -184,6 +192,7 @@ describe('indexForest', () => {
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
injector: undefined,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
tagName: 'Child2_2',
|
||||
@@ -205,12 +214,14 @@ describe('indexForest', () => {
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
injector: undefined,
|
||||
static: false,
|
||||
},
|
||||
],
|
||||
changeDetection: 'ng-eager',
|
||||
controlFlowBlock: null,
|
||||
hasNativeElement: true,
|
||||
injector: undefined,
|
||||
static: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ const indexTree = (
|
||||
hydration: node.hydration,
|
||||
controlFlowBlock: node.controlFlowBlock,
|
||||
changeDetection: node.changeDetection,
|
||||
static: node.static,
|
||||
hasNativeElement: (node as any).hasNativeElement,
|
||||
injector: node.injector,
|
||||
};
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
<div class="node-name" #nodeName>
|
||||
<span
|
||||
class="element-name"
|
||||
[class.non-element]="!node().original.hasNativeElement"
|
||||
[class.non-element]="!node().original.hasNativeElement && !node().static"
|
||||
[class.angular-element]="isElement()"
|
||||
>{{ node().name }}</span
|
||||
>
|
||||
|
||||
+10
-3
@@ -8,7 +8,7 @@
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
&:not(.static):hover {
|
||||
background-color: var(--color-tree-node-hovered);
|
||||
|
||||
.tree-node-info > .hydration {
|
||||
@@ -33,8 +33,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.non-element {
|
||||
color: var(--color-tree-node-non-element-name);
|
||||
&.static {
|
||||
.element-name {
|
||||
color: var(--quaternary-contrast);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-node-info {
|
||||
@@ -66,6 +69,10 @@
|
||||
.node-name {
|
||||
position: relative;
|
||||
|
||||
.non-element {
|
||||
color: var(--color-tree-node-non-element-name);
|
||||
}
|
||||
|
||||
.angular-element {
|
||||
content: '';
|
||||
color: var(--color-tree-node-ng-element);
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ export type NodeTextMatch = {
|
||||
'[class.selected]': 'isSelected',
|
||||
'[class.highlighted]': 'isHighlighted',
|
||||
'[class.new-node]': 'node().newItem',
|
||||
'[class.static]': 'node().static',
|
||||
'(click)': 'selectNode.emit(this.node())',
|
||||
'(dblclick)': 'selectDomElement.emit(this.node())',
|
||||
'(mouseenter)': 'highlightNode.emit(this.node())',
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ const mockIndexedNode: IndexedNode = {
|
||||
},
|
||||
],
|
||||
controlFlowBlock: null,
|
||||
static: false,
|
||||
hasNativeElement: true,
|
||||
children: [],
|
||||
tagName: 'foo',
|
||||
|
||||
+454
File diff suppressed because it is too large
Load Diff
@@ -122,6 +122,7 @@ export interface DevToolsNode<DirType = DirectiveType, CmpType = ComponentType>
|
||||
resolutionPath?: SerializedInjector[];
|
||||
hydration?: HydrationStatus;
|
||||
controlFlowBlock: ControlFlowBlock | null;
|
||||
static: boolean;
|
||||
changeDetection?: ChangeDetection;
|
||||
injector?: Injector;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user