Skip to content

Commit 7569490

Browse files
committed
refactor(core): use ApplicationRef.whenStable instead of a custom util function
This commit removes a custom `whenStable` util in favor of standard `ApplicationRef.whenStable` API. There is also an important different between the custom `whenStable` function and `ApplicationRef.whenStable` implementation: the `whenStable` was caching the "stable" promise on per-ApplicationRef basis, which resulted in unexpected behavior with zoneless, when some code ended up getting a stale resolved promise, when an application was not stable yet, this causing order of operations issues. This commit also has an extra test that covers that case.
1 parent 1bdc3f1 commit 7569490

File tree

10 files changed

+126
-77
lines changed

10 files changed

+126
-77
lines changed

packages/common/http/src/transfer_cache.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
ɵformatRuntimeError as formatRuntimeError,
2020
ɵperformanceMarkFeature as performanceMarkFeature,
2121
ɵtruncateMiddle as truncateMiddle,
22-
ɵwhenStable as whenStable,
2322
ɵRuntimeError as RuntimeError,
2423
} from '@angular/core';
2524
import {isPlatformServer} from '@angular/common';
@@ -338,7 +337,7 @@ export function withHttpTransferCache(cacheOptions: HttpTransferCacheOptions): P
338337
const cacheState = inject(CACHE_OPTIONS);
339338

340339
return () => {
341-
whenStable(appRef).then(() => {
340+
appRef.whenStable().then(() => {
342341
cacheState.isCacheActive = false;
343342
});
344343
};

packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
ɵunwrapSafeValue as unwrapSafeValue,
3232
ChangeDetectorRef,
3333
ApplicationRef,
34-
ɵwhenStable as whenStable,
3534
} from '@angular/core';
3635

3736
import {RuntimeErrorCode} from '../../errors';
@@ -1310,7 +1309,7 @@ function assertNoLoaderParamsWithoutLoader(dir: NgOptimizedImage, imageLoader: I
13101309
async function assetPriorityCountBelowThreshold(appRef: ApplicationRef) {
13111310
if (IMGS_WITH_PRIORITY_ATTR_COUNT === 0) {
13121311
IMGS_WITH_PRIORITY_ATTR_COUNT++;
1313-
await whenStable(appRef);
1312+
await appRef.whenStable();
13141313
if (IMGS_WITH_PRIORITY_ATTR_COUNT > PRIORITY_COUNT_THRESHOLD) {
13151314
console.warn(
13161315
formatRuntimeError(

packages/core/src/application/application_ref.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,10 @@ let whenStableStore: WeakMap<ApplicationRef, Promise<void>> | undefined;
915915
/**
916916
* Returns a Promise that resolves when the application becomes stable after this method is called
917917
* the first time.
918+
*
919+
* Note: this function is unused in the FW code, but it's still present since the CLI code relies
920+
* on it currently (see https://github.com/angular/angular-cli/blob/20411f696eb52c500e096e3dfc5e195185794edc/packages/angular/ssr/src/routes/ng-routes.ts#L435).
921+
* Remove this function once CLI code is updated to use `ApplicationRef.whenStable` instead.
918922
*/
919923
export function whenStable(applicationRef: ApplicationRef): Promise<void> {
920924
whenStableStore ??= new WeakMap();

packages/core/src/hydration/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref';
9+
import {APP_BOOTSTRAP_LISTENER, ApplicationRef} from '../application/application_ref';
1010
import {Console} from '../console';
1111
import {
1212
ENVIRONMENT_INITIALIZER,
@@ -152,7 +152,7 @@ function printHydrationStats(injector: Injector) {
152152
* Returns a Promise that is resolved when an application becomes stable.
153153
*/
154154
function whenStableWithTimeout(appRef: ApplicationRef, injector: Injector): Promise<void> {
155-
const whenStablePromise = whenStable(appRef);
155+
const whenStablePromise = appRef.whenStable();
156156
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
157157
const timeoutTime = APPLICATION_IS_STABLE_TIMEOUT;
158158
const console = injector.get(Console);

packages/core/src/hydration/event_replay.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
EventPhase,
1919
} from '@angular/core/primitives/event-dispatch';
2020

21-
import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref';
21+
import {APP_BOOTSTRAP_LISTENER, ApplicationRef} from '../application/application_ref';
2222
import {ENVIRONMENT_INITIALIZER, Injector} from '../di';
2323
import {inject} from '../di/injector_compatibility';
2424
import {Provider} from '../di/interface/provider';
@@ -134,7 +134,7 @@ export function withEventReplay(): Provider[] {
134134
// Kick off event replay logic once hydration for the initial part
135135
// of the application is completed. This timing is similar to the unclaimed
136136
// dehydrated views cleanup timing.
137-
whenStable(appRef).then(() => {
137+
appRef.whenStable().then(() => {
138138
const eventContractDetails = injector.get(JSACTION_EVENT_CONTRACT);
139139
initEventReplay(eventContractDetails, injector);
140140
const jsActionMap = injector.get(JSACTION_BLOCK_ELEMENT_MAP);

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,6 @@
134134
{
135135
"name": "ElementRef"
136136
},
137-
{
138-
"name": "EmptyError"
139-
},
140137
{
141138
"name": "EmulatedEncapsulationDomRenderer2"
142139
},
@@ -680,9 +677,6 @@
680677
{
681678
"name": "deepForEachProvider"
682679
},
683-
{
684-
"name": "defaultErrorFactory"
685-
},
686680
{
687681
"name": "detachMovedView"
688682
},
@@ -1298,9 +1292,6 @@
12981292
{
12991293
"name": "subscribeOn"
13001294
},
1301-
{
1302-
"name": "throwIfEmpty"
1303-
},
13041295
{
13051296
"name": "throwProviderNotFoundError"
13061297
},
@@ -1337,12 +1328,6 @@
13371328
{
13381329
"name": "walkProviderTree"
13391330
},
1340-
{
1341-
"name": "whenStable"
1342-
},
1343-
{
1344-
"name": "whenStableStore"
1345-
},
13461331
{
13471332
"name": "withDomHydration"
13481333
},

packages/platform-server/src/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
ɵannotateForHydration as annotateForHydration,
2020
ɵIS_HYDRATION_DOM_REUSE_ENABLED as IS_HYDRATION_DOM_REUSE_ENABLED,
2121
ɵSSR_CONTENT_INTEGRITY_MARKER as SSR_CONTENT_INTEGRITY_MARKER,
22-
ɵwhenStable as whenStable,
2322
ɵstartMeasuring as startMeasuring,
2423
ɵstopMeasuring as stopMeasuring,
2524
} from '@angular/core';
@@ -178,8 +177,10 @@ function insertEventRecordScript(
178177
async function _render(platformRef: PlatformRef, applicationRef: ApplicationRef): Promise<string> {
179178
const measuringLabel = 'whenStable';
180179
startMeasuring(measuringLabel);
180+
181181
// Block until application is stable.
182-
await whenStable(applicationRef);
182+
await applicationRef.whenStable();
183+
183184
stopMeasuring(measuringLabel);
184185

185186
const platformState = platformRef.injector.get(PlatformState);

0 commit comments

Comments
 (0)