Skip to content

Commit 2d4262c

Browse files
hawkgsamishne
authored andcommitted
fix(devtools): support for @defer-only blocks; defer declared blocks (#66546)
Add support for `@defer`-only blocks (previously, they weren't rendered in the component tree at all); Fix declared blocks section in the details PR Close #66546
1 parent 92d2498 commit 2d4262c

File tree

7 files changed

+69
-46
lines changed

7 files changed

+69
-46
lines changed

devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
ɵDeferBlockData as DeferBlockData,
1212
ɵHydratedNode as HydrationNode,
1313
} from '@angular/core';
14-
import {CurrentDeferBlock, HydrationStatus} from '../../../../protocol';
14+
import {RenderedDeferBlock, HydrationStatus} from '../../../../protocol';
1515

1616
import {ComponentTreeNode} from '../interfaces';
1717
import {ngDebugClient} from '../ng-debug-api/ng-debug-api';
@@ -126,14 +126,19 @@ function groupDeferChildrenIfNeeded(
126126
getDirectiveMetadata?: FrameworkAgnosticGlobalUtils['getDirectiveMetadata'],
127127
) {
128128
const currentDeferBlock = deferBlocks.currentBlock;
129-
const isFirstDefferedChild = node === currentDeferBlock?.rootNodes[0];
130-
if (isFirstDefferedChild) {
129+
const isFirstDeferredChild = node === currentDeferBlock?.rootNodes[0];
130+
// Handles the case where the @defer is still unresolved but doesn't
131+
// have a placeholder, for instance, by which children we mark
132+
// the position of the block normally. In this case, we use the host.
133+
const isHostNode = node === currentDeferBlock?.hostNode;
134+
135+
if (isFirstDeferredChild || isHostNode) {
131136
deferBlocks.advance();
132137

133-
// When encountering the first child of a defer block
134-
// We create a synthetic TreeNode reprensenting the defer block
138+
// When encountering the first child of a defer block (or the host node),
139+
// we create a synthetic TreeNode representing the defer block.
135140
const childrenTree: ComponentTreeNode[] = [];
136-
currentDeferBlock.rootNodes.forEach((child) => {
141+
for (const child of currentDeferBlock.rootNodes) {
137142
extractViewTree(
138143
child,
139144
childrenTree,
@@ -143,7 +148,7 @@ function groupDeferChildrenIfNeeded(
143148
getDirectives,
144149
getDirectiveMetadata,
145150
);
146-
});
151+
}
147152

148153
const deferBlockTreeNode = {
149154
children: childrenTree,
@@ -155,7 +160,7 @@ function groupDeferChildrenIfNeeded(
155160
defer: {
156161
id: `deferId-${rootId}-${deferBlocks.currentIndex}`,
157162
state: currentDeferBlock.state,
158-
currentBlock: currentBlock(currentDeferBlock),
163+
renderedBlock: getRenderedBlock(currentDeferBlock),
159164
triggers: groupTriggers(currentDeferBlock.triggers),
160165
blocks: {
161166
hasErrorBlock: currentDeferBlock.hasErrorBlock,
@@ -213,12 +218,16 @@ function groupTriggers(triggers: string[]) {
213218
return {defer, hydrate, prefetch};
214219
}
215220

216-
function currentBlock(deferBlock: DeferBlockData): CurrentDeferBlock | null {
221+
function getRenderedBlock(deferBlock: DeferBlockData): RenderedDeferBlock | null {
217222
if (['placeholder', 'loading', 'error'].includes(deferBlock.state)) {
218223
return deferBlock.state as 'placeholder' | 'loading' | 'error';
219224
}
225+
if (deferBlock.state === 'complete') {
226+
return 'defer';
227+
}
220228
return null;
221229
}
230+
222231
export class RTreeStrategy {
223232
supports(): boolean {
224233
return (['getDirectiveMetadata', 'getComponent'] as const).every(
@@ -253,7 +262,7 @@ class DeferBlocksIterator {
253262
this.currentIndex++;
254263
}
255264

256-
get currentBlock() {
265+
get currentBlock(): DeferBlockData | undefined {
257266
return this.blocks[this.currentIndex];
258267
}
259268
}

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/tree-node.component.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,22 @@
2929
</div>
3030

3131
@if (node().onPush) {
32-
<span class="on-push">OnPush</span>
32+
<span class="trait">OnPush</span>
3333
}
3434

3535
@let defer = node().defer;
3636

3737
@if (!defer && (!hydration || hydration.status !== 'dehydrated')) {
3838
<!-- Shown/hidden via CSS -->
39-
<span class="console-reference"> == $ng0 </span>
39+
<span class="console-reference trait"> == $ng0 </span>
4040
}
4141

42-
@if (defer && defer.currentBlock) {
43-
<span class="on-push">(&#64;{{ defer.currentBlock }})</span>
42+
@if (defer) {
43+
@if (defer.renderedBlock && defer.renderedBlock !== 'defer') {
44+
<span class="trait">(@{{ defer.renderedBlock }})</span>
45+
} @else if (!defer.renderedBlock) {
46+
<span class="trait">(non-rendered)</span>
47+
}
4448
}
4549

4650
@switch (hydration?.status) {

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/tree-node.component.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@
9999
display: none;
100100
}
101101

102-
.console-reference,
103-
.on-push {
102+
.trait {
104103
color: var(--color-tree-node-console-ref);
105104
padding-left: 8px;
106105
font-style: italic;

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/tree-node.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ describe('TreeNodeComponent', () => {
9797
});
9898
await fixture.whenStable();
9999

100-
onPush = fixture.debugElement.query(By.css('.on-push'));
100+
onPush = fixture.debugElement.query(By.css('.trait'));
101101

102-
expect(onPush).toBeTruthy();
102+
expect(onPush.nativeElement.textContent).toEqual('OnPush');
103103
});
104104

105105
it('should handle selection', async () => {

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/defer-view/defer-view.component.html

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,36 @@
1-
<mat-toolbar>Current block</mat-toolbar>
1+
<mat-toolbar>Rendered block</mat-toolbar>
22
<div class="defer-details">
3-
<span class="pill"> &#64;{{ defer().currentBlock ?? 'defer' }}</span>
4-
</div>
5-
6-
@let triggers = defer().triggers;
7-
@let blocks = defer().blocks;
8-
<mat-toolbar>Declared blocks</mat-toolbar>
9-
<div class="defer-details">
10-
@if (blocks.placeholderBlock) {
11-
<span class="pill">
12-
&#64;placeholder<!--
13-
-->{{
14-
blocks.placeholderBlock.minimumTime
15-
? `(minimum ${blocks.placeholderBlock.minimumTime} ms)`
16-
: ''
17-
}}</span
18-
>
19-
}
20-
@if (blocks.loadingBlock) {
21-
<span class="pill">&#64;loading{{ loadingBlockInfo() }}</span>
22-
}
23-
@if (blocks.hasErrorBlock) {
24-
<span class="pill">&#64;error</span>
3+
@if (defer().renderedBlock) {
4+
<span class="pill"> &#64;{{ defer().renderedBlock }}</span>
5+
} @else {
6+
<em>Nothing rendered yet</em>
257
}
268
</div>
279

10+
@if (hasDeclaredBlocks()) {
11+
@let blocks = defer().blocks;
12+
<mat-toolbar>Declared blocks</mat-toolbar>
13+
<div class="defer-details">
14+
@if (blocks.placeholderBlock.exists) {
15+
<span class="pill">
16+
&#64;placeholder<!--
17+
-->{{
18+
blocks.placeholderBlock.minimumTime
19+
? `(minimum ${blocks.placeholderBlock.minimumTime} ms)`
20+
: ''
21+
}}</span
22+
>
23+
}
24+
@if (blocks.loadingBlock.exists) {
25+
<span class="pill">&#64;loading{{ loadingBlockInfo() }}</span>
26+
}
27+
@if (blocks.hasErrorBlock) {
28+
<span class="pill">&#64;error</span>
29+
}
30+
</div>
31+
}
32+
33+
@let triggers = defer().triggers;
2834
<mat-toolbar>Triggers</mat-toolbar>
2935
<div class="defer-details">
3036
<h3>Defer triggers</h3>

devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/defer-view/defer-view.component.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class DeferViewComponent {
2222

2323
readonly loadingBlockInfo = computed(() => {
2424
const loadingBlock = this.defer().blocks.loadingBlock;
25-
if (!loadingBlock) {
25+
if (!loadingBlock.exists) {
2626
return null;
2727
}
2828

@@ -35,4 +35,9 @@ export class DeferViewComponent {
3535
}
3636
return info.length ? `(${info.join(', ')})` : null;
3737
});
38+
39+
readonly hasDeclaredBlocks = computed(() => {
40+
const blocks = this.defer().blocks;
41+
return blocks.hasErrorBlock || blocks.placeholderBlock.exists || blocks.loadingBlock.exists;
42+
});
3843
}

devtools/projects/protocol/src/lib/messages.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ export type HydrationStatus =
7878
actualNodeDetails: string | null;
7979
};
8080

81-
export type CurrentDeferBlock = 'placeholder' | 'loading' | 'error';
81+
export type RenderedDeferBlock = 'defer' | 'placeholder' | 'loading' | 'error';
8282

8383
export interface DeferInfo {
8484
id: string;
8585
state: 'placeholder' | 'loading' | 'complete' | 'error' | 'initial';
86-
currentBlock: CurrentDeferBlock | null;
86+
renderedBlock: RenderedDeferBlock | null;
8787
triggers: {
8888
defer: string[];
8989
hydrate: string[];
@@ -94,8 +94,8 @@ export interface DeferInfo {
9494

9595
export interface BlockDetails {
9696
hasErrorBlock: boolean;
97-
placeholderBlock: null | {minimumTime: number | null};
98-
loadingBlock: null | {minimumTime: number | null; afterTime: number | null};
97+
placeholderBlock: {exists: boolean; minimumTime: number | null};
98+
loadingBlock: {exists: boolean; minimumTime: number | null; afterTime: number | null};
9999
}
100100

101101
// TODO: refactor to remove nativeElement as it is not serializable

0 commit comments

Comments
 (0)