diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts index c7f7a2dc3e3eb6..4b88f650114e2b 100644 --- a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts +++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts @@ -84,16 +84,16 @@ export class DocViewer implements OnChanges { // tslint:disable-next-line:no-unused-variable private animateContent = false; - private readonly pendingRenderTasks = inject(PendingTasks); + private readonly pendingTasks = inject(PendingTasks); private countOfExamples = 0; async ngOnChanges(changes: SimpleChanges): Promise { - const taskId = this.pendingRenderTasks.add(); + const taskId = this.pendingTasks.add(); if ('docContent' in changes) { await this.renderContentsAndRunClientSetup(this.docContent!); } - this.pendingRenderTasks.remove(taskId); + this.pendingTasks.remove(taskId); } async renderContentsAndRunClientSetup(content?: string): Promise { @@ -195,7 +195,7 @@ export class DocViewer implements OnChanges { const preview = Boolean(placeholder.getAttribute('preview')); const title = placeholder.getAttribute('header') ?? undefined; const firstCodeSnippetTitle = - snippets.length > 0 ? snippets[0].title ?? snippets[0].name : undefined; + snippets.length > 0 ? (snippets[0].title ?? snippets[0].name) : undefined; const exampleRef = this.viewContainer.createComponent(ExampleViewer); this.countOfExamples++; diff --git a/adev/src/content/guide/zoneless.md b/adev/src/content/guide/zoneless.md index 3d7e34193abf7a..5577b2339bb70f 100644 --- a/adev/src/content/guide/zoneless.md +++ b/adev/src/content/guide/zoneless.md @@ -81,15 +81,15 @@ Zoneless applications. In fact, removing these calls can lead to performance reg are used in applications that still rely on ZoneJS. -### `ExperimentalPendingTasks` for Server Side Rendering (SSR) +### `PendingTasks` for Server Side Rendering (SSR) If you are using SSR with Angular, you may know that it relies on ZoneJS to help determine when the application is "stable" and can be serialized. If there are asynchronous tasks that should prevent serialization, an application -not using ZoneJS will need to make Angular aware of these with the `ExperimentalPendingTasks` service. Serialization +not using ZoneJS will need to make Angular aware of these with the `PendingTasks` service. Serialization will wait for the first moment that all pending tasks have been removed. ```typescript -const taskService = inject(ExperimentalPendingTasks); +const taskService = inject(PendingTasks); const taskCleanup = taskService.add(); await doSomeWorkThatNeedsToBeRendered(); taskCleanup(); diff --git a/adev/src/content/reference/errors/NG0506.md b/adev/src/content/reference/errors/NG0506.md index ad570702edc083..45dd3b49423951 100644 --- a/adev/src/content/reference/errors/NG0506.md +++ b/adev/src/content/reference/errors/NG0506.md @@ -78,4 +78,4 @@ class SimpleComponent { In zoneless scenarios, stability might be delayed by an application code inside of an `effect` running in an infinite loop (potentially because signals used in effect functions keep changing) or a pending HTTP request. -Developers may also explicitly contribute to indicating the application's stability by using the experimental [`PendingTasks`](/api/core/ExperimentalPendingTasks) service. If you use the mentioned APIs in your application, make sure you invoke a function to mark the task as completed. +Developers may also explicitly contribute to indicating the application's stability by using the [`PendingTasks`](/api/core/PendingTasks) service. If you use the mentioned APIs in your application, make sure you invoke a function to mark the task as completed. diff --git a/goldens/public-api/core/index.api.md b/goldens/public-api/core/index.api.md index df68223fd54266..f352e2688655f9 100644 --- a/goldens/public-api/core/index.api.md +++ b/goldens/public-api/core/index.api.md @@ -710,14 +710,6 @@ export interface ExistingSansProvider { useExisting: any; } -// @public -export class ExperimentalPendingTasks { - add(): () => void; - run(fn: () => Promise): Promise; - // (undocumented) - static ɵprov: unknown; -} - // @public export interface FactoryProvider extends FactorySansProvider { multi?: boolean; @@ -1351,6 +1343,14 @@ export interface OutputRefSubscription { // @public @deprecated export const PACKAGE_ROOT_URL: InjectionToken; +// @public +export class PendingTasks { + add(): () => void; + run(fn: () => Promise): Promise; + // (undocumented) + static ɵprov: unknown; +} + // @public export interface Pipe { name: string; diff --git a/packages/common/http/src/interceptor.ts b/packages/common/http/src/interceptor.ts index 20b39fc7d49b03..bf2fcf338e0f09 100644 --- a/packages/common/http/src/interceptor.ts +++ b/packages/common/http/src/interceptor.ts @@ -16,7 +16,7 @@ import { runInInjectionContext, ɵConsole as Console, ɵformatRuntimeError as formatRuntimeError, - ɵPendingTasks as PendingTasks, + ɵPendingTasksInternal as PendingTasks, } from '@angular/core'; import {Observable} from 'rxjs'; import {finalize} from 'rxjs/operators'; diff --git a/packages/core/schematics/BUILD.bazel b/packages/core/schematics/BUILD.bazel index 8b36b7b2d8230f..ed2131db6891e6 100644 --- a/packages/core/schematics/BUILD.bazel +++ b/packages/core/schematics/BUILD.bazel @@ -36,6 +36,7 @@ rollup_bundle( "//packages/core/schematics/ng-generate/standalone-migration:index.ts": "standalone-migration", "//packages/core/schematics/ng-generate/signal-input-migration:index.ts": "signal-input-migration", "//packages/core/schematics/migrations/explicit-standalone-flag:index.ts": "explicit-standalone-flag", + "//packages/core/schematics/migrations/pending-tasks:index.ts": "pending-tasks", }, format = "cjs", link_workspace_root = True, @@ -46,6 +47,7 @@ rollup_bundle( ], deps = [ "//packages/core/schematics/migrations/explicit-standalone-flag", + "//packages/core/schematics/migrations/pending-tasks", "//packages/core/schematics/ng-generate/control-flow-migration", "//packages/core/schematics/ng-generate/inject-migration", "//packages/core/schematics/ng-generate/route-lazy-loading", diff --git a/packages/core/schematics/migrations.json b/packages/core/schematics/migrations.json index b36f6ad5eb1498..dc26c4a0014f3e 100644 --- a/packages/core/schematics/migrations.json +++ b/packages/core/schematics/migrations.json @@ -4,6 +4,11 @@ "version": "19.0.0", "description": "Updates non-standalone Directives, Component and Pipes to standalone:false", "factory": "./bundles/explicit-standalone-flag#migrate" + }, + "pending-tasks": { + "version": "19.0.0", + "description": "Updates ExperimentalPendingTasks to PendingTasks", + "factory": "./bundles/pending-tasks#migrate" } } } diff --git a/packages/core/schematics/migrations/pending-tasks/BUILD.bazel b/packages/core/schematics/migrations/pending-tasks/BUILD.bazel new file mode 100644 index 00000000000000..96ec7a0a5c2e67 --- /dev/null +++ b/packages/core/schematics/migrations/pending-tasks/BUILD.bazel @@ -0,0 +1,21 @@ +load("//tools:defaults.bzl", "ts_library") + +package( + default_visibility = [ + "//packages/core/schematics:__pkg__", + "//packages/core/schematics/migrations/google3:__pkg__", + "//packages/core/schematics/test:__pkg__", + ], +) + +ts_library( + name = "pending-tasks", + srcs = glob(["**/*.ts"]), + tsconfig = "//packages/core/schematics:tsconfig.json", + deps = [ + "//packages/core/schematics/utils", + "@npm//@angular-devkit/schematics", + "@npm//@types/node", + "@npm//typescript", + ], +) diff --git a/packages/core/schematics/migrations/pending-tasks/index.ts b/packages/core/schematics/migrations/pending-tasks/index.ts new file mode 100644 index 00000000000000..30fc65950ccf46 --- /dev/null +++ b/packages/core/schematics/migrations/pending-tasks/index.ts @@ -0,0 +1,58 @@ +/** + * @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 {Rule, SchematicsException, Tree, UpdateRecorder} from '@angular-devkit/schematics'; +import {relative} from 'path'; +import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; +import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; +import {migrateFile} from './migration'; + +export function migrate(): Rule { + return async (tree: Tree) => { + const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree); + const basePath = process.cwd(); + const allPaths = [...buildPaths, ...testPaths]; + + if (!allPaths.length) { + throw new SchematicsException( + 'Could not find any tsconfig file. Cannot run the afterRender phase migration.', + ); + } + + for (const tsconfigPath of allPaths) { + runMigration(tree, tsconfigPath, basePath); + } + }; +} + +function runMigration(tree: Tree, tsconfigPath: string, basePath: string) { + const program = createMigrationProgram(tree, tsconfigPath, basePath); + const sourceFiles = program + .getSourceFiles() + .filter((sourceFile) => canMigrateFile(basePath, sourceFile, program)); + + for (const sourceFile of sourceFiles) { + let update: UpdateRecorder | null = null; + + const rewriter = (startPos: number, width: number, text: string | null) => { + if (update === null) { + // Lazily initialize update, because most files will not require migration. + update = tree.beginUpdate(relative(basePath, sourceFile.fileName)); + } + update.remove(startPos, width); + if (text !== null) { + update.insertLeft(startPos, text); + } + }; + migrateFile(sourceFile, program.getTypeChecker(), rewriter); + + if (update !== null) { + tree.commitUpdate(update); + } + } +} diff --git a/packages/core/schematics/migrations/pending-tasks/migration.ts b/packages/core/schematics/migrations/pending-tasks/migration.ts new file mode 100644 index 00000000000000..6191f906e11a34 --- /dev/null +++ b/packages/core/schematics/migrations/pending-tasks/migration.ts @@ -0,0 +1,67 @@ +/** + * @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 ts from 'typescript'; +import {ChangeTracker} from '../../utils/change_tracker'; +import { + getImportOfIdentifier, + getImportSpecifier, + getNamedImports, +} from '../../utils/typescript/imports'; + +const CORE = '@angular/core'; +const EXPERIMENTAL_PENDING_TASKS = 'ExperimentalPendingTasks'; + +type RewriteFn = (startPos: number, width: number, text: string) => void; + +export function migrateFile( + sourceFile: ts.SourceFile, + typeChecker: ts.TypeChecker, + rewriteFn: RewriteFn, +) { + const changeTracker = new ChangeTracker(ts.createPrinter()); + // Check if there are any imports of the `AfterRenderPhase` enum. + const coreImports = getNamedImports(sourceFile, CORE); + if (!coreImports) { + return; + } + const importSpecifier = getImportSpecifier(sourceFile, CORE, EXPERIMENTAL_PENDING_TASKS); + if (!importSpecifier) { + return; + } + const nodeToReplace = importSpecifier.propertyName ?? importSpecifier.name; + if (!ts.isIdentifier(nodeToReplace)) { + return; + } + + changeTracker.replaceNode(nodeToReplace, ts.factory.createIdentifier('PendingTasks')); + + ts.forEachChild(sourceFile, function visit(node: ts.Node) { + // import handled above + if (ts.isImportDeclaration(node)) { + return; + } + + if ( + ts.isIdentifier(node) && + node.text === EXPERIMENTAL_PENDING_TASKS && + getImportOfIdentifier(typeChecker, node)?.name === EXPERIMENTAL_PENDING_TASKS + ) { + changeTracker.replaceNode(node, ts.factory.createIdentifier('PendingTasks')); + } + + ts.forEachChild(node, visit); + }); + + // Write the changes. + for (const changesInFile of changeTracker.recordChanges().values()) { + for (const change of changesInFile) { + rewriteFn(change.start, change.removeLength ?? 0, change.text); + } + } +} diff --git a/packages/core/schematics/test/pending_tasks_spec.ts b/packages/core/schematics/test/pending_tasks_spec.ts new file mode 100644 index 00000000000000..981b4220818b0e --- /dev/null +++ b/packages/core/schematics/test/pending_tasks_spec.ts @@ -0,0 +1,101 @@ +/** + * @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 {getSystemPath, normalize, virtualFs} from '@angular-devkit/core'; +import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing'; +import {HostTree} from '@angular-devkit/schematics'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; +import {runfiles} from '@bazel/runfiles'; +import shx from 'shelljs'; + +describe('experimental pending tasks migration', () => { + let runner: SchematicTestRunner; + let host: TempScopedNodeJsSyncHost; + let tree: UnitTestTree; + let tmpDirPath: string; + + function writeFile(filePath: string, contents: string) { + host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents)); + } + + function runMigration() { + return runner.runSchematic('pending-tasks', {}, tree); + } + + beforeEach(() => { + runner = new SchematicTestRunner('test', runfiles.resolvePackageRelative('../migrations.json')); + host = new TempScopedNodeJsSyncHost(); + tree = new UnitTestTree(new HostTree(host)); + + writeFile( + '/tsconfig.json', + JSON.stringify({ + compilerOptions: { + lib: ['es2015'], + strictNullChecks: true, + }, + }), + ); + + writeFile( + '/angular.json', + JSON.stringify({ + version: 1, + projects: {t: {root: '', architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}, + }), + ); + + tmpDirPath = getSystemPath(host.root); + + // Switch into the temporary directory path. This allows us to run + // the schematic against our custom unit test tree. + shx.cd(tmpDirPath); + }); + + it('should update ExperimentalPendingTasks', async () => { + writeFile( + '/index.ts', + ` + import {ExperimentalPendingTasks, Directive} from '@angular/core'; + + @Directive({ + selector: '[someDirective]' + }) + export class SomeDirective { + x = inject(ExperimentalPendingTasks); + }`, + ); + + await runMigration(); + + const content = tree.readContent('/index.ts').replace(/\s+/g, ' '); + expect(content).toContain("import {PendingTasks, Directive} from '@angular/core';"); + expect(content).toContain('x = inject(PendingTasks);'); + }); + + it('should update import alias', async () => { + writeFile( + '/index.ts', + ` + import {ExperimentalPendingTasks as Y, Directive} from '@angular/core'; + + @Directive({ + selector: '[someDirective]' + }) + export class SomeDirective { + x = inject(Y); + }`, + ); + + await runMigration(); + + const content = tree.readContent('/index.ts').replace(/\s+/g, ' '); + expect(content).toContain("import {PendingTasks as Y, Directive} from '@angular/core';"); + expect(content).toContain('x = inject(Y);'); + }); +}); diff --git a/packages/core/src/application/application_ref.ts b/packages/core/src/application/application_ref.ts index 96847502eba714..a66206399508da 100644 --- a/packages/core/src/application/application_ref.ts +++ b/packages/core/src/application/application_ref.ts @@ -29,7 +29,7 @@ import {ComponentFactory, ComponentRef} from '../linker/component_factory'; import {ComponentFactoryResolver} from '../linker/component_factory_resolver'; import {NgModuleRef} from '../linker/ng_module_factory'; import {ViewRef} from '../linker/view_ref'; -import {PendingTasks} from '../pending_tasks'; +import {PendingTasksInternal} from '../pending_tasks'; import {RendererFactory2} from '../render/api'; import {AfterRenderManager} from '../render3/after_render/manager'; import {ComponentFactory as R3ComponentFactory} from '../render3/component_ref'; @@ -361,7 +361,7 @@ export class ApplicationRef { /** * Returns an Observable that indicates when the application is stable or unstable. */ - public readonly isStable: Observable = inject(PendingTasks).hasPendingTasks.pipe( + public readonly isStable: Observable = inject(PendingTasksInternal).hasPendingTasks.pipe( map((pending) => !pending), ); diff --git a/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts b/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts index 99e9a28aeebdd7..3829016c0edce7 100644 --- a/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts +++ b/packages/core/src/change_detection/scheduling/ng_zone_scheduling.ts @@ -19,7 +19,7 @@ import { StaticProvider, } from '../../di'; import {RuntimeError, RuntimeErrorCode} from '../../errors'; -import {PendingTasks} from '../../pending_tasks'; +import {PendingTasksInternal} from '../../pending_tasks'; import {performanceMarkFeature} from '../../util/performance'; import {NgZone} from '../../zone'; import {InternalNgZoneOptions} from '../../zone/ng_zone'; @@ -256,7 +256,7 @@ export class ZoneStablePendingTask { private readonly subscription = new Subscription(); private initialized = false; private readonly zone = inject(NgZone); - private readonly pendingTasks = inject(PendingTasks); + private readonly pendingTasks = inject(PendingTasksInternal); initialize() { if (this.initialized) { diff --git a/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts b/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts index 3be5fd3ec8857a..131d68db5582b1 100644 --- a/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts +++ b/packages/core/src/change_detection/scheduling/zoneless_scheduling_impl.ts @@ -14,7 +14,7 @@ import {inject} from '../../di/injector_compatibility'; import {EnvironmentProviders} from '../../di/interface/provider'; import {makeEnvironmentProviders} from '../../di/provider_collection'; import {RuntimeError, RuntimeErrorCode, formatRuntimeError} from '../../errors'; -import {PendingTasks} from '../../pending_tasks'; +import {PendingTasksInternal} from '../../pending_tasks'; import { scheduleCallbackWithMicrotask, scheduleCallbackWithRafRace, @@ -57,7 +57,7 @@ function trackMicrotaskNotificationForDebugging() { @Injectable({providedIn: 'root'}) export class ChangeDetectionSchedulerImpl implements ChangeDetectionScheduler { private readonly appRef = inject(ApplicationRef); - private readonly taskService = inject(PendingTasks); + private readonly taskService = inject(PendingTasksInternal); private readonly ngZone = inject(NgZone); private readonly zonelessEnabled = inject(ZONELESS_ENABLED); private readonly disableScheduling = diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 9c41a6439df2a0..4e3ff2e7f5572d 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -43,7 +43,7 @@ export { NgZoneOptions, } from './change_detection/scheduling/ng_zone_scheduling'; export {provideExperimentalZonelessChangeDetection} from './change_detection/scheduling/zoneless_scheduling_impl'; -export {ExperimentalPendingTasks} from './pending_tasks'; +export {PendingTasks} from './pending_tasks'; export {provideExperimentalCheckNoChangesForDebug} from './change_detection/scheduling/exhaustive_check_no_changes'; export {enableProdMode, isDevMode} from './util/is_dev_mode'; export { diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index b8e44ab84a7c31..1f4caa6a624a36 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -103,7 +103,10 @@ export { resolveComponentResources as ɵresolveComponentResources, restoreComponentResolutionQueue as ɵrestoreComponentResolutionQueue, } from './metadata/resource_loading'; -export {PendingTasks as ɵPendingTasks} from './pending_tasks'; +export { + PendingTasksInternal as ɵPendingTasksInternal, + PendingTasksInternal as ɵPendingTasks, +} from './pending_tasks'; export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS} from './platform/platform'; export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities'; export {AnimationRendererType as ɵAnimationRendererType} from './render/api'; diff --git a/packages/core/src/defer/instructions.ts b/packages/core/src/defer/instructions.ts index 459aa5e7b7015d..53d55ed926e00b 100644 --- a/packages/core/src/defer/instructions.ts +++ b/packages/core/src/defer/instructions.ts @@ -15,7 +15,7 @@ import {internalImportProvidersFrom} from '../di/provider_collection'; import {RuntimeError, RuntimeErrorCode} from '../errors'; import {findMatchingDehydratedView} from '../hydration/views'; import {populateDehydratedViewsInLContainer} from '../linker/view_container_ref'; -import {PendingTasks} from '../pending_tasks'; +import {PendingTasksInternal} from '../pending_tasks'; import {assertLContainer, assertTNodeForLView} from '../render3/assert'; import {bindingUpdated} from '../render3/bindings'; import {ChainedInjector} from '../render3/chained_injector'; @@ -910,7 +910,7 @@ export function triggerResourceLoading( } // Indicate that an application is not stable and has a pending task. - const pendingTasks = injector.get(PendingTasks); + const pendingTasks = injector.get(PendingTasksInternal); const taskId = pendingTasks.add(); // The `dependenciesFn` might be `null` when all dependencies within diff --git a/packages/core/src/event_emitter.ts b/packages/core/src/event_emitter.ts index c28abb883b213c..29077b91528e54 100644 --- a/packages/core/src/event_emitter.ts +++ b/packages/core/src/event_emitter.ts @@ -13,7 +13,7 @@ import {OutputRef} from './authoring/output/output_ref'; import {isInInjectionContext} from './di/contextual'; import {inject} from './di/injector_compatibility'; import {DestroyRef} from './linker/destroy_ref'; -import {PendingTasks} from './pending_tasks'; +import {PendingTasksInternal} from './pending_tasks'; /** * Use in components with the `@Output` directive to emit custom events @@ -112,7 +112,7 @@ export interface EventEmitter extends Subject, OutputRef { class EventEmitter_ extends Subject implements OutputRef { __isAsync: boolean; // tslint:disable-line destroyRef: DestroyRef | undefined = undefined; - private readonly pendingTasks: PendingTasks | undefined = undefined; + private readonly pendingTasks: PendingTasksInternal | undefined = undefined; constructor(isAsync: boolean = false) { super(); @@ -122,7 +122,7 @@ class EventEmitter_ extends Subject implements OutputRef { // For backwards compatibility reasons, this cannot be required. if (isInInjectionContext()) { this.destroyRef = inject(DestroyRef, {optional: true}) ?? undefined; - this.pendingTasks = inject(PendingTasks, {optional: true}) ?? undefined; + this.pendingTasks = inject(PendingTasksInternal, {optional: true}) ?? undefined; } } diff --git a/packages/core/src/pending_tasks.ts b/packages/core/src/pending_tasks.ts index e6b91c4f4c8297..a655fb4ede934f 100644 --- a/packages/core/src/pending_tasks.ts +++ b/packages/core/src/pending_tasks.ts @@ -19,7 +19,7 @@ import { /** * Internal implementation of the pending tasks service. */ -export class PendingTasks implements OnDestroy { +export class PendingTasksInternal implements OnDestroy { private taskId = 0; private pendingTasks = new Set(); private get _hasPendingTasks() { @@ -52,14 +52,14 @@ export class PendingTasks implements OnDestroy { /** @nocollapse */ static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({ - token: PendingTasks, + token: PendingTasksInternal, providedIn: 'root', - factory: () => new PendingTasks(), + factory: () => new PendingTasksInternal(), }); } /** - * Experimental service that keeps track of pending tasks contributing to the stableness of Angular + * 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. @@ -71,21 +71,17 @@ export class PendingTasks implements OnDestroy { * * @usageNotes * ```typescript - * const pendingTasks = inject(ExperimentalPendingTasks); + * const pendingTasks = inject(PendingTasks); * 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 + * @developerPreview */ -export class ExperimentalPendingTasks { - private internalPendingTasks = inject(PendingTasks); +export class PendingTasks { + private internalPendingTasks = inject(PendingTasksInternal); private scheduler = inject(ChangeDetectionScheduler); /** * Adds a new task that should block application's stability. @@ -131,8 +127,8 @@ export class ExperimentalPendingTasks { /** @nocollapse */ static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({ - token: ExperimentalPendingTasks, + token: PendingTasks, providedIn: 'root', - factory: () => new ExperimentalPendingTasks(), + factory: () => new PendingTasks(), }); } diff --git a/packages/core/src/render3/reactivity/root_effect_scheduler.ts b/packages/core/src/render3/reactivity/root_effect_scheduler.ts index 54147bb50a1363..b006dadebfab90 100644 --- a/packages/core/src/render3/reactivity/root_effect_scheduler.ts +++ b/packages/core/src/render3/reactivity/root_effect_scheduler.ts @@ -7,7 +7,7 @@ */ import {ɵɵdefineInjectable} from '../../di/interface/defs'; -import {PendingTasks} from '../../pending_tasks'; +import {PendingTasksInternal} from '../../pending_tasks'; import {inject} from '../../di/injector_compatibility'; /** @@ -51,7 +51,7 @@ export abstract class EffectScheduler { export class ZoneAwareEffectScheduler implements EffectScheduler { private queuedEffectCount = 0; private queues = new Map>(); - private readonly pendingTasks = inject(PendingTasks); + private readonly pendingTasks = inject(PendingTasksInternal); protected taskId: number | null = null; schedule(handle: SchedulableEffect): void { diff --git a/packages/core/test/acceptance/pending_tasks_spec.ts b/packages/core/test/acceptance/pending_tasks_spec.ts index 84d9ba0fd60613..9ed36f37c940c7 100644 --- a/packages/core/test/acceptance/pending_tasks_spec.ts +++ b/packages/core/test/acceptance/pending_tasks_spec.ts @@ -6,16 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {ApplicationRef, ExperimentalPendingTasks} from '@angular/core'; +import {ApplicationRef, PendingTasks} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {EMPTY, firstValueFrom, of} from 'rxjs'; import {filter, map, take, withLatestFrom} from 'rxjs/operators'; -import {PendingTasks} from '../../src/pending_tasks'; +import {PendingTasksInternal} from '../../src/pending_tasks'; describe('PendingTasks', () => { it('should wait until all tasks are completed', async () => { - const pendingTasks = TestBed.inject(PendingTasks); + const pendingTasks = TestBed.inject(PendingTasksInternal); const taskA = pendingTasks.add(); const taskB = pendingTasks.add(); const taskC = pendingTasks.add(); @@ -27,7 +27,7 @@ describe('PendingTasks', () => { }); it('should allow calls to remove the same task multiple times', async () => { - const pendingTasks = TestBed.inject(PendingTasks); + const pendingTasks = TestBed.inject(PendingTasksInternal); expect(await hasPendingTasks(pendingTasks)).toBeFalse(); const taskA = pendingTasks.add(); @@ -41,7 +41,7 @@ describe('PendingTasks', () => { }); it('should be tolerant to removal of non-existent ids', async () => { - const pendingTasks = TestBed.inject(PendingTasks); + const pendingTasks = TestBed.inject(PendingTasksInternal); expect(await hasPendingTasks(pendingTasks)).toBeFalse(); pendingTasks.remove(Math.random()); @@ -53,7 +53,7 @@ describe('PendingTasks', () => { it('contributes to applicationRef stableness', async () => { const appRef = TestBed.inject(ApplicationRef); - const pendingTasks = TestBed.inject(PendingTasks); + const pendingTasks = TestBed.inject(PendingTasksInternal); const taskA = pendingTasks.add(); await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false); @@ -67,10 +67,10 @@ describe('PendingTasks', () => { }); }); -describe('public ExperimentalPendingTasks', () => { +describe('public PendingTasks', () => { it('should allow adding and removing tasks influencing stability', async () => { const appRef = TestBed.inject(ApplicationRef); - const pendingTasks = TestBed.inject(ExperimentalPendingTasks); + const pendingTasks = TestBed.inject(PendingTasks); const removeTaskA = pendingTasks.add(); await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false); @@ -83,7 +83,7 @@ describe('public ExperimentalPendingTasks', () => { it('should allow blocking stability with run', async () => { const appRef = TestBed.inject(ApplicationRef); - const pendingTasks = TestBed.inject(ExperimentalPendingTasks); + const pendingTasks = TestBed.inject(PendingTasks); let resolveFn: () => void; pendingTasks.run(() => { @@ -98,7 +98,7 @@ describe('public ExperimentalPendingTasks', () => { it('should return the result of the run function', async () => { const appRef = TestBed.inject(ApplicationRef); - const pendingTasks = TestBed.inject(ExperimentalPendingTasks); + const pendingTasks = TestBed.inject(PendingTasks); const result = await pendingTasks.run(async () => { await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false); @@ -112,7 +112,7 @@ describe('public ExperimentalPendingTasks', () => { xit('should stop blocking stability if run promise rejects', async () => { const appRef = TestBed.inject(ApplicationRef); - const pendingTasks = TestBed.inject(ExperimentalPendingTasks); + const pendingTasks = TestBed.inject(PendingTasks); let rejectFn: () => void; const task = pendingTasks.run(() => { @@ -133,7 +133,7 @@ function applicationRefIsStable(applicationRef: ApplicationRef) { return firstValueFrom(applicationRef.isStable); } -function hasPendingTasks(pendingTasks: PendingTasks): Promise { +function hasPendingTasks(pendingTasks: PendingTasksInternal): Promise { return of(EMPTY) .pipe( withLatestFrom(pendingTasks.hasPendingTasks), diff --git a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json index ad38b5fb37b12a..cf69c1ffcd0156 100644 --- a/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations-standalone/bundle.golden_symbols.json @@ -441,7 +441,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "R3Injector" diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index 169346cb79ecbb..ce61f3f56d2635 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -474,7 +474,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "PlatformRef" diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 63e6cf5160dcf4..e2ecd318934994 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -360,7 +360,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "PlatformRef" diff --git a/packages/core/test/bundling/defer/bundle.golden_symbols.json b/packages/core/test/bundling/defer/bundle.golden_symbols.json index b35a7baa1e554a..a386ad0008037d 100644 --- a/packages/core/test/bundling/defer/bundle.golden_symbols.json +++ b/packages/core/test/bundling/defer/bundle.golden_symbols.json @@ -411,7 +411,7 @@ "name": "PRESERVE_HOST_CONTENT_DEFAULT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "R3Injector" diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 1ca75f5337cb9e..7abb0969aa5c16 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -492,7 +492,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "PlatformRef" diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 8c16fa3c311b60..2285a2194fe1af 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -477,7 +477,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "PlatformRef" diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 7a777ab62aec00..c46f47e4cc3806 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -273,7 +273,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "PlatformRef" diff --git a/packages/core/test/bundling/hydration/bundle.golden_symbols.json b/packages/core/test/bundling/hydration/bundle.golden_symbols.json index 6e7713f18bd123..962fa62f32f23a 100644 --- a/packages/core/test/bundling/hydration/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hydration/bundle.golden_symbols.json @@ -399,7 +399,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "R3Injector" diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index 9320125602371e..39913532399d6f 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -108,7 +108,7 @@ "name": "Observable" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "R3Injector" diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 66c82964b21719..379b72ed3bc9a8 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -537,7 +537,7 @@ "name": "PathLocationStrategy" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "PlatformLocation" diff --git a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json index 640da0f65dfc20..e97ea30d3efb0b 100644 --- a/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json +++ b/packages/core/test/bundling/standalone_bootstrap/bundle.golden_symbols.json @@ -330,7 +330,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "R3Injector" diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 32275469ba7256..e96af6eb5c4127 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -381,7 +381,7 @@ "name": "PRESERVE_HOST_CONTENT" }, { - "name": "PendingTasks" + "name": "PendingTasksInternal" }, { "name": "PlatformRef" diff --git a/packages/core/test/defer_fixture_spec.ts b/packages/core/test/defer_fixture_spec.ts index d53636806bb1ab..ae1d383b8ea111 100644 --- a/packages/core/test/defer_fixture_spec.ts +++ b/packages/core/test/defer_fixture_spec.ts @@ -7,7 +7,7 @@ */ import {ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common'; -import {Component, PLATFORM_ID, ɵPendingTasks as PendingTasks} from '@angular/core'; +import {Component, PLATFORM_ID, ɵPendingTasksInternal as PendingTasks} from '@angular/core'; import {DeferBlockBehavior, DeferBlockState, TestBed} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; diff --git a/packages/core/test/event_emitter_spec.ts b/packages/core/test/event_emitter_spec.ts index fa69100f463e54..3d73b2e0b092dc 100644 --- a/packages/core/test/event_emitter_spec.ts +++ b/packages/core/test/event_emitter_spec.ts @@ -11,8 +11,6 @@ import {filter, tap} from 'rxjs/operators'; import {EventEmitter} from '../src/event_emitter'; import {ApplicationRef} from '../public_api'; -import {firstValueFrom} from 'rxjs'; -import {PendingTasks} from '../src/pending_tasks'; describe('EventEmitter', () => { let emitter: EventEmitter; diff --git a/packages/core/testing/src/component_fixture.ts b/packages/core/testing/src/component_fixture.ts index c273389403f8c3..838c6ad5c7c42e 100644 --- a/packages/core/testing/src/component_fixture.ts +++ b/packages/core/testing/src/component_fixture.ts @@ -23,7 +23,7 @@ import { ɵgetDeferBlocks as getDeferBlocks, ɵNoopNgZone as NoopNgZone, ɵZONELESS_ENABLED as ZONELESS_ENABLED, - ɵPendingTasks as PendingTasks, + ɵPendingTasksInternal as PendingTasks, ɵEffectScheduler as EffectScheduler, ɵMicrotaskEffectScheduler as MicrotaskEffectScheduler, } from '@angular/core'; diff --git a/packages/platform-server/test/integration_spec.ts b/packages/platform-server/test/integration_spec.ts index 808751fa80351c..799a6d79aba8e5 100644 --- a/packages/platform-server/test/integration_spec.ts +++ b/packages/platform-server/test/integration_spec.ts @@ -40,7 +40,7 @@ import { TransferState, Type, ViewEncapsulation, - ɵPendingTasks as PendingTasks, + ɵPendingTasksInternal as PendingTasks, ɵwhenStable as whenStable, } from '@angular/core'; import {SSR_CONTENT_INTEGRITY_MARKER} from '@angular/core/src/hydration/utils'; diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 2a03393a21a31c..385bc02605dbd8 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -12,7 +12,7 @@ import { Injectable, Type, ɵConsole as Console, - ɵPendingTasks as PendingTasks, + ɵPendingTasksInternal as PendingTasks, ɵRuntimeError as RuntimeError, } from '@angular/core'; import {Observable, Subject, Subscription, SubscriptionLike} from 'rxjs';