feat(core): support APP_INITIALIZER work with observable (#33222)
This commit adds support for Observables that now can be used as a part of APP_INITIALIZER. Previously, only Primises were supported. Closes #15088. PR Close #33222
This commit is contained in:
parent
53c65f468f
commit
ca17ac523c
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, Injectable, InjectionToken, Optional} from './di';
|
import {Inject, Injectable, InjectionToken, Optional} from './di';
|
||||||
import {isPromise} from './util/lang';
|
import {isObservable, isPromise} from './util/lang';
|
||||||
import {noop} from './util/noop';
|
import {noop} from './util/noop';
|
||||||
|
|
||||||
|
|
||||||
@ -16,8 +16,8 @@ import {noop} from './util/noop';
|
|||||||
* one or more initialization functions.
|
* one or more initialization functions.
|
||||||
*
|
*
|
||||||
* The provided functions are injected at application startup and executed during
|
* The provided functions are injected at application startup and executed during
|
||||||
* app initialization. If any of these functions returns a Promise, initialization
|
* app initialization. If any of these functions returns a Promise or an Observable, initialization
|
||||||
* does not complete until the Promise is resolved.
|
* does not complete until the Promise is resolved or the Observable is completed.
|
||||||
*
|
*
|
||||||
* You can, for example, create a factory function that loads language data
|
* You can, for example, create a factory function that loads language data
|
||||||
* or an external configuration, and provide that function to the `APP_INITIALIZER` token.
|
* or an external configuration, and provide that function to the `APP_INITIALIZER` token.
|
||||||
@ -68,6 +68,11 @@ export class ApplicationInitStatus {
|
|||||||
const initResult = this.appInits[i]();
|
const initResult = this.appInits[i]();
|
||||||
if (isPromise(initResult)) {
|
if (isPromise(initResult)) {
|
||||||
asyncInitPromises.push(initResult);
|
asyncInitPromises.push(initResult);
|
||||||
|
} else if (isObservable(initResult)) {
|
||||||
|
const observableAsPromise = new Promise<void>((resolve, reject) => {
|
||||||
|
initResult.subscribe({complete: resolve, error: reject});
|
||||||
|
});
|
||||||
|
asyncInitPromises.push(observableAsPromise);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {Injector} from '@angular/core';
|
|
||||||
import {APP_INITIALIZER, ApplicationInitStatus} from '@angular/core/src/application_init';
|
import {APP_INITIALIZER, ApplicationInitStatus} from '@angular/core/src/application_init';
|
||||||
|
import {Observable, Subscriber} from 'rxjs';
|
||||||
|
|
||||||
import {inject, TestBed, waitForAsync} from '../testing';
|
import {inject, TestBed, waitForAsync} from '../testing';
|
||||||
|
|
||||||
@ -28,50 +28,152 @@ import {inject, TestBed, waitForAsync} from '../testing';
|
|||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with async initializers', () => {
|
describe('with async promise initializers', () => {
|
||||||
let resolve: (result: any) => void;
|
let resolve: (result: any) => void;
|
||||||
|
let reject: (reason?: any) => void;
|
||||||
let promise: Promise<any>;
|
let promise: Promise<any>;
|
||||||
let completerResolver = false;
|
let initFnInvoked = false;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
let initializerFactory = (injector: Injector) => {
|
promise = new Promise((res, rej) => {
|
||||||
return () => {
|
|
||||||
const initStatus = injector.get(ApplicationInitStatus);
|
|
||||||
initStatus.donePromise.then(() => {
|
|
||||||
expect(completerResolver).toBe(true);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
promise = new Promise((res) => {
|
|
||||||
resolve = res;
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
});
|
});
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
{provide: APP_INITIALIZER, multi: true, useValue: () => promise},
|
{provide: APP_INITIALIZER, multi: true, useValue: () => promise},
|
||||||
{
|
|
||||||
provide: APP_INITIALIZER,
|
|
||||||
multi: true,
|
|
||||||
useFactory: initializerFactory,
|
|
||||||
deps: [Injector]
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the status once all async initializers are done',
|
it('should update the status once all async promise initializers are done',
|
||||||
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||||
|
// Accessing internal `runInitializers` function of the `ApplicationInitStatus` class
|
||||||
|
// instance for testing purposes to invoke initializer functions.
|
||||||
(status as any).runInitializers();
|
(status as any).runInitializers();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
completerResolver = true;
|
initFnInvoked = true;
|
||||||
resolve(null);
|
resolve(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(status.done).toBe(false);
|
expect(status.done).toBe(false);
|
||||||
status.donePromise.then(() => {
|
status.donePromise.then(() => {
|
||||||
expect(status.done).toBe(true);
|
expect(status.done).toBe(true);
|
||||||
expect(completerResolver).toBe(true);
|
expect(initFnInvoked).toBe(true);
|
||||||
});
|
});
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
it('should handle a case when promise is rejected',
|
||||||
|
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||||
|
// Accessing internal `runInitializers` function of the `ApplicationInitStatus` class
|
||||||
|
// instance for testing purposes to invoke initializer functions.
|
||||||
|
(status as any).runInitializers();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
initFnInvoked = true;
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status.done).toBe(false);
|
||||||
|
status.donePromise
|
||||||
|
.then(
|
||||||
|
() => fail('`donePromise.then` should not be invoked when promise is rejected'))
|
||||||
|
.catch(() => {
|
||||||
|
expect(status.done).toBe(false);
|
||||||
|
expect(initFnInvoked).toBe(true);
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with app initializers represented using observables', () => {
|
||||||
|
let subscriber: Subscriber<any>;
|
||||||
|
let observable: Observable<any>;
|
||||||
|
let initFnInvoked = false;
|
||||||
|
beforeEach(() => {
|
||||||
|
observable = new Observable((res) => {
|
||||||
|
subscriber = res;
|
||||||
|
});
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{provide: APP_INITIALIZER, multi: true, useValue: () => observable},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the status once all async observable initializers are completed',
|
||||||
|
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||||
|
// Accessing internal `runInitializers` function of the `ApplicationInitStatus` class
|
||||||
|
// instance for testing purposes to invoke initializer functions.
|
||||||
|
(status as any).runInitializers();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
initFnInvoked = true;
|
||||||
|
subscriber.complete();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status.done).toBe(false);
|
||||||
|
status.donePromise.then(() => {
|
||||||
|
expect(status.done).toBe(true);
|
||||||
|
expect(initFnInvoked).toBe(true);
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
|
||||||
|
it('should update the status once all async observable initializers nexted and completed',
|
||||||
|
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||||
|
// Accessing internal `runInitializers` function of the `ApplicationInitStatus` class
|
||||||
|
// instance for testing purposes to invoke initializer functions.
|
||||||
|
(status as any).runInitializers();
|
||||||
|
|
||||||
|
subscriber.next('one');
|
||||||
|
subscriber.next('two');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
initFnInvoked = true;
|
||||||
|
subscriber.complete();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status.done).toBe(false);
|
||||||
|
status.donePromise.then(() => {
|
||||||
|
expect(status.done).toBe(true);
|
||||||
|
expect(initFnInvoked).toBe(true);
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
|
||||||
|
it('should update the status if all async observable initializers are completed before runInitializers',
|
||||||
|
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||||
|
subscriber.complete();
|
||||||
|
// Accessing internal `runInitializers` function of the `ApplicationInitStatus` class
|
||||||
|
// instance for testing purposes to invoke initializer functions.
|
||||||
|
(status as any).runInitializers();
|
||||||
|
|
||||||
|
expect(status.done).toBe(false);
|
||||||
|
|
||||||
|
status.donePromise.then(() => {
|
||||||
|
expect(status.done).toBe(true);
|
||||||
|
});
|
||||||
|
})));
|
||||||
|
|
||||||
|
it('should handle a case when observable emits an error',
|
||||||
|
waitForAsync(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
|
||||||
|
// Accessing internal `runInitializers` function of the `ApplicationInitStatus` class
|
||||||
|
// instance for testing purposes to invoke initializer functions.
|
||||||
|
(status as any).runInitializers();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
initFnInvoked = true;
|
||||||
|
subscriber.error();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(status.done).toBe(false);
|
||||||
|
status.donePromise
|
||||||
|
.then(
|
||||||
|
() => fail(
|
||||||
|
'`donePromise.then` should not be invoked when observable emits an error'))
|
||||||
|
.catch(() => {
|
||||||
|
expect(status.done).toBe(false);
|
||||||
|
expect(initFnInvoked).toBe(true);
|
||||||
|
});
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1253,6 +1253,9 @@
|
|||||||
{
|
{
|
||||||
"name": "isObject"
|
"name": "isObject"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "isObservable"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "isOptionsObj"
|
"name": "isOptionsObj"
|
||||||
},
|
},
|
||||||
|
@ -1580,6 +1580,9 @@
|
|||||||
{
|
{
|
||||||
"name": "isObject"
|
"name": "isObject"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "isObservable"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "isPositive"
|
"name": "isPositive"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user