8000 fix(core): properly handle app stabilization with defer blocks (#61056) · angular/angular@400dbc5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 400dbc5

Browse files
alan-agius4AndrewKushnir
authored andcommitted
fix(core): properly handle app stabilization with defer blocks (#61056)
Previously, the app was marked as stable prematurely. For more details, see #61038 (comment) Closes: #61038 (cherry picked from commit de649c9) PR Close #61056
1 parent 90b3355 commit 400dbc5

File tree

10 files changed

+32
-48
lines changed

10 files changed

+32
-48
lines changed

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

+1-2
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

+1-1
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

+1-1
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/prerender.ts

+4-22
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,10 @@ const browserDistFolder = resolve(serverDistFolder, '../browser');
1717
const indexHtml = readFileSync(join(browserDistFolder, 'index.csr.html'), 'utf-8');
1818

1919
async function runTest() {
20-
// Test and validate the errors are printed in the console.
21-
const originalConsoleError = console.error;
22-
const errors: string[] = [];
23-
console.error = (error, data) => errors.push(error.toString() + ' ' + data.toString());
24-
25-
try {
26-
await renderApplication(bootstrap, {
27-
document: indexHtml,
28-
url: '/error',
29-
});
30-
} catch {}
31-
32-
console.error = originalConsoleError;
33-
34-
// Test case
35-
if (!errors.some((e) => e.includes('Error in resolver'))) {
36-
errors.forEach(console.error);
37-
console.error(
38-
'\nError: expected rendering errors ("Error in resolver") to be printed in the console.\n',
39-
);
40-
process.exit(1);
41-
}
20+
await renderApplication(bootstrap, {
21+
document: indexHtml,
22+
url: '/error',
23+
});
4224
}
4325

4426
runTest();

integration/platform-server-zoneless/projects/standalone/src/app/app.config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import {provideHttpClient} from '@angular/common/http';
2-
import {ApplicationConfig, provideZonelessChangeDetection} from '@angular/core';
2+
import {ApplicationConfig, provideExperimentalZonelessChangeDetection} from '@angular/core';
33
import {provideClientHydration, withIncrementalHydration} from '@angular/platform-browser';
44
import {provideRouter} from '@angular/router';
55

66
import {routes} from './app.routes';
77

88
export const appConfig: ApplicationConfig = {
99
providers: [
10-
provideZonelessChangeDetection(),
10+
provideExperimentalZonelessChangeDetection(),
1111
provideRouter(routes),
1212
provideClientHydration(withIncrementalHydration()),
1313
provideHttpClient(),

integration/platform-server-zoneless/projects/standalone/src/app/app.routes.ts

-5
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ export const routes: Routes = [
2929
{
3030
path: 'error',
3131
component: HelloWorldComponent,
32-
resolve: {
33-
'id': () => {
34-
throw new Error('Error in resolver.');
35-
},
36-
},
3732
},
3833
{
3934
path: 'defer',

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

+5-3
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

+7-2
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

+10-10
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
/**

packages/core/test/bundling/defer/bundle.golden_symbols.json

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
"PREFETCH_TRIGGER_CLEANUP_FNS",
138138
"PRESERVE_HOST_CONTENT",
139139
"PRESERVE_HOST_CONTENT_DEFAULT",
140+
"PendingTasks",
140141
"PendingTasksInternal",
141142
"R3Injector",
142143
"REACTIVE_LVIEW_CONSUMER_NODE",

0 commit comments

Comments
 (0)
0