Skip to content

Commit

Permalink
refactor(core): use ApplicationRef.whenStable instead of a custom u…
Browse files Browse the repository at this point in the history
…til 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.
  • Loading branch information
AndrewKushnir committed Nov 23, 2024
1 parent 1bdc3f1 commit 7569490
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 77 deletions.
3 changes: 1 addition & 2 deletions packages/common/http/src/transfer_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
ɵformatRuntimeError as formatRuntimeError,
ɵperformanceMarkFeature as performanceMarkFeature,
ɵtruncateMiddle as truncateMiddle,
ɵwhenStable as whenStable,
ɵRuntimeError as RuntimeError,
} from '@angular/core';
import {isPlatformServer} from '@angular/common';
Expand Down Expand Up @@ -338,7 +337,7 @@ export function withHttpTransferCache(cacheOptions: HttpTransferCacheOptions): P
const cacheState = inject(CACHE_OPTIONS);

return () => {
whenStable(appRef).then(() => {
appRef.whenStable().then(() => {
cacheState.isCacheActive = false;
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
ɵunwrapSafeValue as unwrapSafeValue,
ChangeDetectorRef,
ApplicationRef,
ɵwhenStable as whenStable,
} from '@angular/core';

import {RuntimeErrorCode} from '../../errors';
Expand Down Expand Up @@ -1310,7 +1309,7 @@ function assertNoLoaderParamsWithoutLoader(dir: NgOptimizedImage, imageLoader: I
async function assetPriorityCountBelowThreshold(appRef: ApplicationRef) {
if (IMGS_WITH_PRIORITY_ATTR_COUNT === 0) {
IMGS_WITH_PRIORITY_ATTR_COUNT++;
await whenStable(appRef);
await appRef.whenStable();
if (IMGS_WITH_PRIORITY_ATTR_COUNT > PRIORITY_COUNT_THRESHOLD) {
console.warn(
formatRuntimeError(
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/application/application_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,10 @@ let whenStableStore: WeakMap<ApplicationRef, Promise<void>> | undefined;
/**
* Returns a Promise that resolves when the application becomes stable after this method is called
* the first time.
*
* Note: this function is unused in the FW code, but it's still present since the CLI code relies
* on it currently (see https://github.com/angular/angular-cli/blob/20411f696eb52c500e096e3dfc5e195185794edc/packages/angular/ssr/src/routes/ng-routes.ts#L435).
* Remove this function once CLI code is updated to use `ApplicationRef.whenStable` instead.
*/
export function whenStable(applicationRef: ApplicationRef): Promise<void> {
whenStableStore ??= new WeakMap();
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/hydration/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/

import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref';
import {APP_BOOTSTRAP_LISTENER, ApplicationRef} from '../application/application_ref';
import {Console} from '../console';
import {
ENVIRONMENT_INITIALIZER,
Expand Down Expand Up @@ -152,7 +152,7 @@ function printHydrationStats(injector: Injector) {
* Returns a Promise that is resolved when an application becomes stable.
*/
function whenStableWithTimeout(appRef: ApplicationRef, injector: Injector): Promise<void> {
const whenStablePromise = whenStable(appRef);
const whenStablePromise = appRef.whenStable();
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
const timeoutTime = APPLICATION_IS_STABLE_TIMEOUT;
const console = injector.get(Console);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/hydration/event_replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
EventPhase,
} from '@angular/core/primitives/event-dispatch';

import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref';
import {APP_BOOTSTRAP_LISTENER, ApplicationRef} from '../application/application_ref';
import {ENVIRONMENT_INITIALIZER, Injector} from '../di';
import {inject} from '../di/injector_compatibility';
import {Provider} from '../di/interface/provider';
Expand Down Expand Up @@ -134,7 +134,7 @@ export function withEventReplay(): Provider[] {
// Kick off event replay logic once hydration for the initial part
// of the application is completed. This timing is similar to the unclaimed
// dehydrated views cleanup timing.
whenStable(appRef).then(() => {
appRef.whenStable().then(() => {
const eventContractDetails = injector.get(JSACTION_EVENT_CONTRACT);
initEventReplay(eventContractDetails, injector);
const jsActionMap = injector.get(JSACTION_BLOCK_ELEMENT_MAP);
Expand Down
15 changes: 0 additions & 15 deletions packages/core/test/bundling/hydration/bundle.golden_symbols.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,6 @@
{
"name": "ElementRef"
},
{
"name": "EmptyError"
},
{
"name": "EmulatedEncapsulationDomRenderer2"
},
Expand Down Expand Up @@ -680,9 +677,6 @@
{
"name": "deepForEachProvider"
},
{
"name": "defaultErrorFactory"
},
{
"name": "detachMovedView"
},
Expand Down Expand Up @@ -1298,9 +1292,6 @@
{
"name": "subscribeOn"
},
{
"name": "throwIfEmpty"
},
{
"name": "throwProviderNotFoundError"
},
Expand Down Expand Up @@ -1337,12 +1328,6 @@
{
"name": "walkProviderTree"
},
{
"name": "whenStable"
},
{
"name": "whenStableStore"
},
{
"name": "withDomHydration"
},
Expand Down
5 changes: 3 additions & 2 deletions packages/platform-server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
ɵannotateForHydration as annotateForHydration,
ɵIS_HYDRATION_DOM_REUSE_ENABLED as IS_HYDRATION_DOM_REUSE_ENABLED,
ɵSSR_CONTENT_INTEGRITY_MARKER as SSR_CONTENT_INTEGRITY_MARKER,
ɵwhenStable as whenStable,
ɵstartMeasuring as startMeasuring,
ɵstopMeasuring as stopMeasuring,
} from '@angular/core';
Expand Down Expand Up @@ -178,8 +177,10 @@ function insertEventRecordScript(
async function _render(platformRef: PlatformRef, applicationRef: ApplicationRef): Promise<string> {
const measuringLabel = 'whenStable';
startMeasuring(measuringLabel);

// Block until application is stable.
await whenStable(applicationRef);
await applicationRef.whenStable();

stopMeasuring(measuringLabel);

const platformState = platformRef.injector.get(PlatformState);
Expand Down
Loading

0 comments on commit 7569490

Please sign in to comment.