-
Notifications
You must be signed in to change notification settings - Fork 1
/
provide-async.function.ts
150 lines (145 loc) · 5.09 KB
/
provide-async.function.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { StaticProvider, inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
import { AsyncInjector } from '../injector/async-injector';
import { AsyncProviderTypes } from '../interfaces/async-provider-types';
import { AsyncStaticProvider } from '../interfaces/async-static-provider';
import { ASYNC_INJECTOR_INITIALIZER } from '../tokens/async-injector-initializer.token';
/**
* It is used to declare one or more async providers. For each provider, it requires the token, and then an async function that can be `useAsyncValue`, `useAsyncClass` or `useAsyncFactory`. It supports `multi` providers as well. It can be used in environment injectors, modules, components and directives. If multiple providers need to be declared in the same injector, use a single `provideAsync` function with multiple providers instead of using it multiple times.
*
* @example
*
* Example of declaring a single async provider:
* ```ts
* bootstrapApplication(AppComponent, {
* providers: [
* provideAsync({
* provide: MY_SERVICE,
* useAsyncClass: () => import('./my-service').then((x) => x.MyService),
* }),
* ],
* });
* ```
*
* @example
*
* Example of declaring multiple providers, each one with different async functions:
* ```ts
* bootstrapApplication(AppComponent, {
* providers: [
* provideAsync(
* {
* provide: CLASS_PROVIDER,
* useAsyncClass: () => import('./first-service').then((x) => x.FirstService),
* },
* {
* provide: VALUE_PROVIDER,
* useAsyncValue: () => import('./value').then((x) => x.value),
* },
* {
* provide: FACTORY_PROVIDER,
* useAsyncFactory: () => import('./factory').then((x) => x.providerFactory),
* }
* ),
* ],
* });
*
* // first-service.ts
* export class FirstService {}
*
* // value.ts
* export const value = 'value';
*
* // factory.ts
* export async function providerFactory() {
* return await Promise.resolve('value');
* }
* ```
*
* @example
*
* Multi providers can also be declared as it happens with Angular:
*
* ```ts
* bootstrapApplication(AppComponent, {
* providers: [
* provideAsync(
* {
* provide: VALUE_PROVIDER,
* useAsyncValue: () => import('./first-value').then((x) => x.value),
* multi: true
* },
* {
* provide: VALUE_PROVIDER,
* useAsyncValue: () => import('./second-value').then((x) => x.value),
* multi: true
* },
* ),
* ],
* });
* ```
*
* @example
*
* Finally, the lazy load behavior can be controlled by the `mode` flag. By default it is `lazy`, which means it won't be resolved until requested. `eager` on the contrary will trigger the load on declaration, even though resolvers are still needed to wait for completion. Example:
*
*```ts
* bootstrapApplication(AppComponent, {
* providers: [
* provideAsync(
* {
* provide: VALUE_PROVIDER,
* useAsyncValue: () => import('./first-value').then((x) => x.value),
* mode: 'eager'
* },
* ),
* ],
* });
* ```
*
* @example
*
* When using a factory provider, the function itself can be async. Regular `inject` function from Angular can be used before executing any async code since the injection context is preserved, however it can't be used afterwards. To solve that problem, and also to protect against cyclic dependencies between async providers, the factory provider function is called with a context that exposes two functions that are self explanatory, `inject` and `resolve`. Example:
*
* ```ts
* import { InjectionContext } from '@nx-squeezer/ngx-async-injector';
*
* export async function providerFactory({ inject, resolve }: InjectionContext): Promise<string> {
* const firstString = await resolve(FIRST_INJECTION_TOKEN);
* const secondString = inject(SECOND_INJECTION_TOKEN);
* return `${firstString} ${secondString}`;
* }
* ```
*/
export function provideAsync<T>(asyncStaticProvider: AsyncStaticProvider<T>): StaticProvider[];
/**
* Overload for a collection of async providers.
*/
export function provideAsync<T extends unknown[]>(
...asyncStaticProviders: AsyncProviderTypes<[...T]>
): StaticProvider[];
/**
* Base implementation.
*/
export function provideAsync(...asyncStaticProviders: AsyncStaticProvider<unknown>[]): StaticProvider[] {
const asyncProviders: StaticProvider[] = asyncStaticProviders.map(
(asyncStaticProvider: AsyncStaticProvider<unknown>) => ({
provide: asyncStaticProvider.provide,
useFactory: () => inject(AsyncInjector, { self: true }).get(asyncStaticProvider.provide),
})
);
const envInitializer: StaticProvider = {
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: () => {
inject(ASYNC_INJECTOR_INITIALIZER, { self: true });
},
};
const asyncInitializer: StaticProvider = {
provide: ASYNC_INJECTOR_INITIALIZER,
useFactory: () => {
const asyncInjector = inject(AsyncInjector, { self: true });
asyncInjector.init(...asyncStaticProviders);
},
};
return [{ provide: AsyncInjector }, envInitializer, asyncInitializer, ...asyncProviders];
}