Skip to content

Commit de649c9

Browse files
committed
fix(core): properly handle app stabilization with defer blocks
Previously, the app was marked as stable prematurely. For more details, see #61038 (comment) Closes: #61038
1 parent 58f99a4 commit de649c9

File tree

6 files changed

+25
-19
lines changed

6 files changed

+25
-19
lines changed

integration/platform-server-zoneless/e2e/src/defer-spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {browser, by, element} from 'protractor';
22
import {bootstrapClientApp, navigateTo, verifyNoBrowserErrors} from './util';
33

4-
// TODO: this does not work with zoneless
5-
xdescribe('Defer E2E Tests', () => {
4+
describe('Defer E2E Tests', () => {
65
beforeEach(async () => {
76
// Don't wait for Angular since it is not bootstrapped automatically.
87
await browser.waitForAngularEnabled(false);

integration/platform-server-zoneless/e2e/src/http-transferstate-lazy-on-init-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {browser, by, element} from 'protractor';
1010
import {bootstrapClientApp, navigateTo, verifyNoBrowserErrors} from './util';
1111

1212
// TODO: this does not work with zoneless
13-
xdescribe('Http TransferState Lazy On Init', () => {
13+
describe('Http TransferState Lazy On Init', () => {
1414
beforeEach(async () => {
1515
// Don't wait for Angular since it is not bootstrapped automatically.
1616
await browser.waitForAngularEnabled(false);

integration/platform-server-zoneless/e2e/src/http-transferstate-lazy-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {browser, by, element} from 'protractor';
1010
import {bootstrapClientApp, navigateTo, verifyNoBrowserErrors} from './util';
1111

1212
// TODO: this does not work with zoneless
13-
xdescribe('Http TransferState Lazy', () => {
13+
describe('Http TransferState Lazy', () => {
1414
beforeEach(async () => {
1515
// Don't wait for Angular since it is not bootstrapped automatically.
1616
await browser.waitForAngularEnabled(false);

integration/platform-server-zoneless/projects/standalone/src/app/http-transferstate-lazy-on-init/http-transfer-state-on-init.component.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,25 @@
77
*/
88

99
import {HttpClient} from '@angular/common/http';
10-
import {Component, OnInit} from '@angular/core';
10+
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
1111

1212
@Component({
1313
selector: 'transfer-state-http',
1414
standalone: true,
1515
template: ` <div class="one">{{ responseOne }}</div> `,
1616
providers: [HttpClient],
17+
changeDetection: ChangeDetectionStrategy.OnPush,
1718
})
1819
export class TransferStateOnInitComponent implements OnInit {
1920
responseOne: string = '';
20-
21-
constructor(private readonly httpClient: HttpClient) {}
21+
private readonly httpClient: HttpClient = inject(HttpClient);
22+
private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef);
2223

2324
ngOnInit(): void {
2425
// Test that HTTP cache works when HTTP call is made in a lifecycle hook.
2526
this.httpClient.get<any>('http://localhost:4206/api').subscribe((response) => {
2627
this.responseOne = response.data;
28+
this.cdr.markForCheck();
2729
});
2830
}
2931
}

integration/platform-server-zoneless/projects/standalone/src/app/http-transferstate-lazy/http-transfer-state.component.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {HttpClient} from '@angular/common/http';
10-
import {Component, OnInit} from '@angular/core';
10+
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
1111

1212
@Component({
1313
selector: 'transfer-state-http',
@@ -17,22 +17,27 @@ import {Component, OnInit} from '@angular/core';
1717
<div class="two">{{ responseTwo }}</div>
1818
`,
1919
providers: [HttpClient],
20+
changeDetection: ChangeDetectionStrategy.OnPush,
2021
})
2122
export class TransferStateComponent implements OnInit {
2223
responseOne: string = '';
2324
responseTwo: string = '';
25+
private readonly httpClient: HttpClient = inject(HttpClient);
26+
private readonly cdr: ChangeDetectorRef = inject(ChangeDetectorRef);
2427

25-
constructor(private readonly httpClient: HttpClient) {
28+
constructor() {
2629
// Test that HTTP cache works when HTTP call is made in the constructor.
2730
this.httpClient.get<any>('http://localhost:4206/api').subscribe((response) => {
2831
this.responseOne = response.data;
32+
this.cdr.markForCheck();
2933
});
3034
}
3135

3236
ngOnInit(): void {
3337
// Test that HTTP cache works when HTTP call is made in a lifecycle hook.
3438
this.httpClient.get<any>('/api-2').subscribe((response) => {
3539
this.responseTwo = response.data;
40+
this.cdr.markForCheck();
3641
});
3742
}
3843
}

packages/core/src/defer/triggering.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
getParentBlockHydrationQueue,
2222
isIncrementalHydrationEnabled,
2323
} from '../hydration/utils';
24-
import {PendingTasksInternal} from '../pending_tasks';
24+
import {PendingTasks, PendingTasksInternal} from '../pending_tasks';
2525
import {assertLContainer} from '../render3/assert';
2626
import {getComponentDef, getDirectiveDef, getPipeDef} from '../render3/def_getters';
2727
import {getTemplateLocationDetails} from '../render3/instructions/element_validation';
@@ -205,8 +205,7 @@ export function triggerResourceLoading(
205205
}
206206

207207
// Indicate that an application is not stable and has a pending task.
208-
const pendingTasks = injector.get(PendingTasksInternal);
209-
const taskId = pendingTasks.add();
208+
const removeTask = injector.get(PendingTasks).add();
210209

211210
// The `dependenciesFn` might be `null` when all dependencies within
212211
// a given defer block were eagerly referenced elsewhere in a file,
@@ -215,7 +214,7 @@ export function triggerResourceLoading(
215214
tDetails.loadingPromise = Promise.resolve().then(() => {
216215
tDetails.loadingPromise = null;
217216
tDetails.loadingState = DeferDependenciesLoadingState.COMPLETE;
218-
pendingTasks.remove(taskId);
217+
removeTask();
219218
});
220219
return tDetails.loadingPromise;
221220
}
@@ -244,11 +243,6 @@ export function triggerResourceLoading(
244243
}
245244
}
246245

247-
// Loading is completed, we no longer need the loading Promise
248-
// and a pending task should also be removed.
249-
tDetails.loadingPromise = null;
250-
pendingTasks.remove(taskId);
251-
252246
if (failed) {
253247
tDetails.loadingState = DeferDependenciesLoadingState.FAILED;
254248

@@ -288,7 +282,13 @@ export function triggerResourceLoading(
288282
}
289283
}
290284
});
291-
return tDetails.loadingPromise;
285+
286+
return tDetails.loadingPromise.finally(() => {
287+
// Loading is completed, we no longer need the loading Promise
288+
// and a pending task should also be removed.
289+
tDetails.loadingPromise = null;
290+
removeTask();
291+
});
292292
}
293293

294294
/**

0 commit comments

Comments
 (0)