mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(core): Add whenStable helper on ApplicationRef (#57190)
This commit adds a `whenStable` function to `ApplicationRef` to cover the most common use-case for the `isStable` observable. PR Close #57190
This commit is contained in:
committed by
Jessica Janiuk
parent
f9a97c710d
commit
7919982063
@@ -137,6 +137,8 @@ export class ApplicationRef {
|
||||
tick(): void;
|
||||
get viewCount(): number;
|
||||
// (undocumented)
|
||||
whenStable(): Promise<void>;
|
||||
// (undocumented)
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ApplicationRef, never>;
|
||||
// (undocumented)
|
||||
static ɵprov: i0.ɵɵInjectableDeclaration<ApplicationRef>;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
setActiveConsumer,
|
||||
setThrowInvalidWriteToSignalError,
|
||||
} from '@angular/core/primitives/signals';
|
||||
import {Observable, Subject} from 'rxjs';
|
||||
import {Observable, Subject, Subscription} from 'rxjs';
|
||||
import {first, map} from 'rxjs/operators';
|
||||
|
||||
import {ZONELESS_ENABLED} from '../change_detection/scheduling/zoneless_scheduling';
|
||||
@@ -347,6 +347,24 @@ export class ApplicationRef {
|
||||
map((pending) => !pending),
|
||||
);
|
||||
|
||||
/**
|
||||
* @returns A promise that resolves when the application becomes stable
|
||||
*/
|
||||
whenStable(): Promise<void> {
|
||||
let subscription: Subscription;
|
||||
return new Promise<void>((resolve) => {
|
||||
subscription = this.isStable.subscribe({
|
||||
next: (stable) => {
|
||||
if (stable) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
}).finally(() => {
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
private readonly _injector = inject(EnvironmentInjector);
|
||||
/**
|
||||
* The `EnvironmentInjector` used to create this application.
|
||||
|
||||
@@ -1383,7 +1383,7 @@ describe('after render hooks', () => {
|
||||
const fixture = TestBed.createComponent(TestCmp);
|
||||
const appRef = TestBed.inject(ApplicationRef);
|
||||
appRef.attachView(fixture.componentRef.hostView);
|
||||
await firstValueFrom(appRef.isStable.pipe(filter((stable) => stable)));
|
||||
await appRef.whenStable();
|
||||
expect(fixture.nativeElement.innerText).toBe('1');
|
||||
});
|
||||
|
||||
|
||||
@@ -277,8 +277,8 @@ describe('Angular with zoneless enabled', () => {
|
||||
expect(fixture.nativeElement.innerText).toEqual('');
|
||||
});
|
||||
|
||||
function whenStable(applicationRef = TestBed.inject(ApplicationRef)): Promise<boolean> {
|
||||
return firstValueFrom(applicationRef.isStable.pipe(filter((stable) => stable)));
|
||||
function whenStable(): Promise<void> {
|
||||
return TestBed.inject(ApplicationRef).whenStable();
|
||||
}
|
||||
|
||||
it(
|
||||
@@ -316,14 +316,14 @@ describe('Angular with zoneless enabled', () => {
|
||||
],
|
||||
});
|
||||
const appViewRef = (applicationRef as any)._views[0] as {context: App; rootNodes: any[]};
|
||||
await whenStable(applicationRef);
|
||||
await applicationRef.whenStable();
|
||||
|
||||
const component2 = createComponent(DynamicCmp, {
|
||||
environmentInjector: applicationRef.injector,
|
||||
});
|
||||
appViewRef.context.viewContainer.insert(component2.hostView);
|
||||
expect(isStable(applicationRef.injector)).toBe(false);
|
||||
await whenStable(applicationRef);
|
||||
await applicationRef.whenStable();
|
||||
component2.destroy();
|
||||
|
||||
// destroying the view synchronously removes element from DOM when not using animations
|
||||
@@ -333,7 +333,7 @@ describe('Angular with zoneless enabled', () => {
|
||||
|
||||
let checkCountBeforeStable = doCheckCount;
|
||||
let renderCountBeforeStable = renderHookCalls;
|
||||
await whenStable(applicationRef);
|
||||
await applicationRef.whenStable();
|
||||
// The view should not have refreshed
|
||||
expect(doCheckCount).toEqual(checkCountBeforeStable);
|
||||
// but render hooks should have run
|
||||
|
||||
@@ -203,7 +203,7 @@ describe('EventEmitter', () => {
|
||||
},
|
||||
});
|
||||
emitter.emit(1);
|
||||
await firstValueFrom(TestBed.inject(ApplicationRef).isStable.pipe(filter((stable) => stable)));
|
||||
await TestBed.inject(ApplicationRef).whenStable();
|
||||
expect(emitValue!).toBeDefined();
|
||||
expect(emitValue!).toEqual(1);
|
||||
});
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
ɵPendingTasks as PendingTasks,
|
||||
} from '@angular/core';
|
||||
import {Subject, Subscription} from 'rxjs';
|
||||
import {first} from 'rxjs/operators';
|
||||
|
||||
import {DeferBlockFixture} from './defer';
|
||||
import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone} from './test_bed_common';
|
||||
@@ -136,13 +135,10 @@ export abstract class ComponentFixture<T> {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.appErrorHandler.whenStableRejectFunctions.add(reject);
|
||||
this._appRef.isStable
|
||||
.pipe(first((stable) => stable))
|
||||
.toPromise()
|
||||
.then((v) => {
|
||||
this.appErrorHandler.whenStableRejectFunctions.delete(reject);
|
||||
resolve(v);
|
||||
});
|
||||
this._appRef.whenStable().then(() => {
|
||||
this.appErrorHandler.whenStableRejectFunctions.delete(reject);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
NgZone,
|
||||
PLATFORM_ID,
|
||||
} from '@angular/core';
|
||||
import {merge, Observable, of} from 'rxjs';
|
||||
import {delay, filter, take} from 'rxjs/operators';
|
||||
import {merge, from, Observable, of} from 'rxjs';
|
||||
import {delay, take} from 'rxjs/operators';
|
||||
|
||||
import {NgswCommChannel} from './low_level';
|
||||
import {SwPush} from './push';
|
||||
@@ -76,9 +76,10 @@ export function ngswAppInitializer(
|
||||
readyToRegister$ = delayWithTimeout(+args[0] || 0);
|
||||
break;
|
||||
case 'registerWhenStable':
|
||||
const whenStable$ = from(injector.get(ApplicationRef).whenStable());
|
||||
readyToRegister$ = !args[0]
|
||||
? whenStable(injector)
|
||||
: merge(whenStable(injector), delayWithTimeout(+args[0]));
|
||||
? whenStable$
|
||||
: merge(whenStable$, delayWithTimeout(+args[0]));
|
||||
break;
|
||||
default:
|
||||
// Unknown strategy.
|
||||
@@ -108,11 +109,6 @@ function delayWithTimeout(timeout: number): Observable<unknown> {
|
||||
return of(null).pipe(delay(timeout));
|
||||
}
|
||||
|
||||
function whenStable(injector: Injector): Observable<unknown> {
|
||||
const appRef = injector.get(ApplicationRef);
|
||||
return appRef.isStable.pipe(filter((stable) => stable));
|
||||
}
|
||||
|
||||
export function ngswCommChannelFactory(
|
||||
opts: SwRegistrationOptions,
|
||||
platformId: string,
|
||||
|
||||
@@ -30,8 +30,7 @@ const serviceWorkerModuleApi = 'ServiceWorkerModule';
|
||||
let swRegisterSpy: jasmine.Spy;
|
||||
|
||||
const untilStable = () => {
|
||||
const appRef: ApplicationRef = TestBed.inject(ApplicationRef);
|
||||
return appRef.isStable.pipe(filter(Boolean), take(1)).toPromise();
|
||||
return TestBed.inject(ApplicationRef).whenStable();
|
||||
};
|
||||
|
||||
beforeEach(
|
||||
@@ -170,6 +169,7 @@ const serviceWorkerModuleApi = 'ServiceWorkerModule';
|
||||
provide: ApplicationRef,
|
||||
useValue: {
|
||||
isStable: isStableSub.asObservable(),
|
||||
whenStable: () => isStableSub.pipe(filter(Boolean), take(1)),
|
||||
afterTick: new Subject(),
|
||||
onDestroy: () => {},
|
||||
},
|
||||
@@ -189,6 +189,7 @@ const serviceWorkerModuleApi = 'ServiceWorkerModule';
|
||||
provide: ApplicationRef,
|
||||
useValue: {
|
||||
isStable: isStableSub.asObservable(),
|
||||
whenStable: () => isStableSub.pipe(filter(Boolean), take(1)),
|
||||
afterTick: new Subject(),
|
||||
onDestroy: () => {},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user