Skip to content

Commit ac863de

Browse files
feat(core): provide ExperimentalPendingTasks API (angular#55487)
The new ExperimentalPendingTasks API lets developers to add and remove tasks that control applications stability: a pending task prevents application from being stable. This API is important for all the use-cases that depend on the concept of stability and SSR serialization is a notable example. Closes angular#53381 PR Close angular#55487
1 parent d0c2e1b commit ac863de

File tree

5 files changed

+75
-13
lines changed

5 files changed

+75
-13
lines changed

goldens/public-api/core/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
```ts
66

7+
import { BehaviorSubject } from 'rxjs';
78
import { Observable } from 'rxjs';
89
import { SIGNAL } from '@angular/core/primitives/signals';
910
import { SignalNode } from '@angular/core/primitives/signals';
@@ -673,6 +674,17 @@ export interface ExistingSansProvider {
673674
useExisting: any;
674675
}
675676

677+
// @public
678+
export class ExperimentalPendingTasks {
679+
add(): () => void;
680+
// (undocumented)
681+
internalPendingTasks: ɵPendingTasks;
682+
// (undocumented)
683+
static ɵfac: i0.ɵɵFactoryDeclaration<ExperimentalPendingTasks, never>;
684+
// (undocumented)
685+
static ɵprov: i0.ɵɵInjectableDeclaration<ExperimentalPendingTasks>;
686+
}
687+
676688
// @public
677689
export interface FactoryProvider extends FactorySansProvider {
678690
multi?: boolean;

packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler {
225225
* ```
226226
*
227227
* This API is experimental. Neither the shape, nor the underlying behavior is stable and can change
228-
* in patch versions. There are known feature gaps, including the lack of a public zoneless API
229-
* which prevents the application from serializing too early with SSR.
228+
* in patch versions. There are known feature gaps and API ergonomic considerations. We will iterate
229+
* on the exact API based on the feedback and our understanding of the problem and solution space.
230230
*
231231
* @publicApi
232232
* @experimental

packages/core/src/core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export {PlatformRef} from './platform/platform_ref';
2929
export {createPlatform, createPlatformFactory, assertPlatform, destroyPlatform, getPlatform} from './platform/platform';
3030
export {provideZoneChangeDetection, NgZoneOptions} from './change_detection/scheduling/ng_zone_scheduling';
3131
export {provideExperimentalZonelessChangeDetection} from './change_detection/scheduling/zoneless_scheduling_impl';
32+
export {ExperimentalPendingTasks} from './pending_tasks';
3233
export {enableProdMode, isDevMode} from './util/is_dev_mode';
3334
export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, PLATFORM_ID, ANIMATION_MODULE_TYPE, CSP_NONCE} from './application/application_tokens';
3435
export {APP_INITIALIZER, ApplicationInitStatus} from './application/application_init';

packages/core/src/pending_tasks.ts

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,16 @@
88

99
import {BehaviorSubject} from 'rxjs';
1010

11-
import {Injectable} from './di';
11+
import {inject} from './di';
12+
import {Injectable} from './di/injectable';
1213
import {OnDestroy} from './interface/lifecycle_hooks';
1314

1415
/**
15-
* *Internal* service that keeps track of pending tasks happening in the system.
16-
*
17-
* This information is needed to make sure that the serialization on the server
18-
* is delayed until all tasks in the queue (such as an initial navigation or a
19-
* pending HTTP request) are completed.
20-
*
21-
* Pending tasks continue to contribute to the stableness of `ApplicationRef`
22-
* throughout the lifetime of the application.
16+
* Internal implementation of the pending tasks service.
2317
*/
24-
@Injectable({providedIn: 'root'})
18+
@Injectable({
19+
providedIn: 'root',
20+
})
2521
export class PendingTasks implements OnDestroy {
2622
private taskId = 0;
2723
private pendingTasks = new Set<number>();
@@ -53,3 +49,44 @@ export class PendingTasks implements OnDestroy {
5349
}
5450
}
5551
}
52+
53+
/**
54+
* Experimental service that keeps track of pending tasks contributing to the stableness of Angular
55+
* application. While several existing Angular services (ex.: `HttpClient`) will internally manage
56+
* tasks influencing stability, this API gives control over stability to library and application
57+
* developers for specific cases not covered by Angular internals.
58+
*
59+
* The concept of stability comes into play in several important scenarios:
60+
* - SSR process needs to wait for the application stability before serializing and sending rendered
61+
* HTML;
62+
* - tests might want to delay assertions until the application becomes stable;
63+
*
64+
* @usageNotes
65+
* ```typescript
66+
* const pendingTasks = inject(ExperimentalPendingTasks);
67+
* const taskCleanup = pendingTasks.add();
68+
* // do work that should block application's stability and then:
69+
* taskCleanup();
70+
* ```
71+
*
72+
* This API is experimental. Neither the shape, nor the underlying behavior is stable and can change
73+
* in patch versions. We will iterate on the exact API based on the feedback and our understanding
74+
* of the problem and solution space.
75+
*
76+
* @publicApi
77+
* @experimental
78+
*/
79+
@Injectable({
80+
providedIn: 'root',
81+
})
82+
export class ExperimentalPendingTasks {
83+
internalPendingTasks = inject(PendingTasks);
84+
/**
85+
* Adds a new task that should block application's stability.
86+
* @returns A cleanup function that removes a task when called.
87+
*/
88+
add(): () => void {
89+
const taskId = this.internalPendingTasks.add();
90+
return () => this.internalPendingTasks.remove(taskId);
91+
}
92+
}

packages/core/test/acceptance/pending_tasks_spec.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {ApplicationRef, ExperimentalPendingTasks} from '@angular/core';
910
import {TestBed} from '@angular/core/testing';
1011
import {EMPTY, of} from 'rxjs';
1112
import {map, take, withLatestFrom} from 'rxjs/operators';
1213

13-
import {ApplicationRef} from '../../src/application/application_ref';
1414
import {PendingTasks} from '../../src/pending_tasks';
1515

1616
describe('PendingTasks', () => {
@@ -67,6 +67,18 @@ describe('PendingTasks', () => {
6767
});
6868
});
6969

70+
describe('public ExperimentalPendingTasks', () => {
71+
it('should allow adding and removing tasks influencing stability', async () => {
72+
const appRef = TestBed.inject(ApplicationRef);
73+
const pendingTasks = TestBed.inject(ExperimentalPendingTasks);
74+
75+
const taskA = pendingTasks.add();
76+
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false);
77+
taskA();
78+
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(true);
79+
});
80+
});
81+
7082
function applicationRefIsStable(applicationRef: ApplicationRef) {
7183
return applicationRef.isStable.pipe(take(1)).toPromise();
7284
}

0 commit comments

Comments
 (0)