angular/packages/core/src/pending_tasks.ts
Andrew Scott dbd0fa00f8 fix(core): async EventEmitter should contribute to app stability (#56308)
async `EventEmitter` should contribute to app stability.

fixes #56290

PR Close #56308
2024-06-11 15:10:21 -07:00

100 lines
3.1 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {BehaviorSubject} from 'rxjs';
import {inject} from './di/injector_compatibility';
import {ɵɵdefineInjectable} from './di/interface/defs';
import {OnDestroy} from './interface/lifecycle_hooks';
/**
* Internal implementation of the pending tasks service.
*/
export class PendingTasks implements OnDestroy {
private taskId = 0;
private pendingTasks = new Set<number>();
private get _hasPendingTasks() {
return this.hasPendingTasks.value;
}
hasPendingTasks = new BehaviorSubject<boolean>(false);
add(): number {
if (!this._hasPendingTasks) {
this.hasPendingTasks.next(true);
}
const taskId = this.taskId++;
this.pendingTasks.add(taskId);
return taskId;
}
remove(taskId: number): void {
this.pendingTasks.delete(taskId);
if (this.pendingTasks.size === 0 && this._hasPendingTasks) {
this.hasPendingTasks.next(false);
}
}
ngOnDestroy(): void {
this.pendingTasks.clear();
if (this._hasPendingTasks) {
this.hasPendingTasks.next(false);
}
}
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({
token: PendingTasks,
providedIn: 'root',
factory: () => new PendingTasks(),
});
}
/**
* Experimental service that keeps track of pending tasks contributing to the stableness of Angular
* application. While several existing Angular services (ex.: `HttpClient`) will internally manage
* tasks influencing stability, this API gives control over stability to library and application
* developers for specific cases not covered by Angular internals.
*
* The concept of stability comes into play in several important scenarios:
* - SSR process needs to wait for the application stability before serializing and sending rendered
* HTML;
* - tests might want to delay assertions until the application becomes stable;
*
* @usageNotes
* ```typescript
* const pendingTasks = inject(ExperimentalPendingTasks);
* const taskCleanup = pendingTasks.add();
* // do work that should block application's stability and then:
* taskCleanup();
* ```
*
* This API is experimental. Neither the shape, nor the underlying behavior is stable and can change
* in patch versions. We will iterate on the exact API based on the feedback and our understanding
* of the problem and solution space.
*
* @publicApi
* @experimental
*/
export class ExperimentalPendingTasks {
private internalPendingTasks = inject(PendingTasks);
/**
* Adds a new task that should block application's stability.
* @returns A cleanup function that removes a task when called.
*/
add(): () => void {
const taskId = this.internalPendingTasks.add();
return () => this.internalPendingTasks.remove(taskId);
}
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({
token: ExperimentalPendingTasks,
providedIn: 'root',
factory: () => new ExperimentalPendingTasks(),
});
}