Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions goldens/public-api/core/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ export interface ExistingSansProvider {
// @public
export class ExperimentalPendingTasks {
add(): () => void;
run<T>(fn: () => Promise<T>): Promise<T>;
// (undocumented)
static ɵprov: unknown;
}
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/pending_tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,35 @@ export class ExperimentalPendingTasks {
};
}

/**
* Runs an asynchronous function and blocks the application's stability until the function completes.
*
* ```
* pendingTasks.run(async () => {
* const userData = await fetch('/api/user');
* this.userData.set(userData);
* });
* ```
*
* Application stability is at least delayed until the next tick after the `run` method resolves
* so it is safe to make additional updates to application state that would require UI synchronization:
*
* ```
* const userData = await pendingTasks.run(() => fetch('/api/user'));
* this.userData.set(userData);
* ```
*
* @param fn The asynchronous function to execute
*/
async run<T>(fn: () => Promise<T>): Promise<T> {
const removeTask = this.add();
try {
return await fn();
} finally {
removeTask();
}
}

/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({
token: ExperimentalPendingTasks,
Expand Down
53 changes: 50 additions & 3 deletions packages/core/test/acceptance/pending_tasks_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import {ApplicationRef, ExperimentalPendingTasks} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {EMPTY, of} from 'rxjs';
import {map, take, withLatestFrom} from 'rxjs/operators';
import {EMPTY, firstValueFrom, of} from 'rxjs';
import {filter, map, take, withLatestFrom} from 'rxjs/operators';

import {PendingTasks} from '../../src/pending_tasks';

Expand Down Expand Up @@ -80,10 +80,57 @@ describe('public ExperimentalPendingTasks', () => {
TestBed.inject(ApplicationRef).tick();
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(true);
});

it('should allow blocking stability with run', async () => {
const appRef = TestBed.inject(ApplicationRef);
const pendingTasks = TestBed.inject(ExperimentalPendingTasks);

let resolveFn: () => void;
pendingTasks.run(() => {
return new Promise<void>((r) => {
resolveFn = r;
});
});
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false);
resolveFn!();
await expectAsync(TestBed.inject(ApplicationRef).whenStable()).toBeResolved();
});

it('should return the result of the run function', async () => {
const appRef = TestBed.inject(ApplicationRef);
const pendingTasks = TestBed.inject(ExperimentalPendingTasks);

const result = await pendingTasks.run(async () => {
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false);
return 1;
});

expect(result).toBe(1);
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false);
await expectAsync(TestBed.inject(ApplicationRef).whenStable()).toBeResolved();
});

xit('should stop blocking stability if run promise rejects', async () => {
const appRef = TestBed.inject(ApplicationRef);
const pendingTasks = TestBed.inject(ExperimentalPendingTasks);

let rejectFn: () => void;
const task = pendingTasks.run(() => {
return new Promise<void>((_, reject) => {
rejectFn = reject;
});
});
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(false);
try {
rejectFn!();
await task;
} catch {}
await expectAsync(applicationRefIsStable(appRef)).toBeResolvedTo(true);
});
});

function applicationRefIsStable(applicationRef: ApplicationRef) {
return applicationRef.isStable.pipe(take(1)).toPromise();
return firstValueFrom(applicationRef.isStable);
}

function hasPendingTasks(pendingTasks: PendingTasks): Promise<boolean> {
Expand Down