New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
asynchronous providers #23279
Comments
Having exactly this problem currently! ConfigService initialized with APP_INITIALIZER, but it's too late because some services are bootstrapped beforehand. :( |
@mischkl ... but isn't it just a different organizational point of view ... because:
|
The problem is that some services are defined in other modules and imported into the app module, in which case they are initialized first. |
@mischkl ... hmmm, I don't think so ... |
@mlc-mlapis that isn't the problem with my project AFAIK. In my case I am using Ngrx, and the Effects in a (non-lazy-loaded) feature module depend on services which in turn try to retrieve the backend URL from the ConfigService in their constructors. On the other hand, if I were to inject the service into a component ("classic" Angular as opposed to redux) it wouldn't be a problem because components are initialized after APP_INITIALIZER. In my experience it is particularly a problem when using Ngrx Effects. As the plunkr demonstrates there may be other ways to reproduce the problem, but at least when using Effects I haven't found a way to solve the problem via structural changes. There are workarounds, of course. One is to dynamically grab the values on every service method call. Another is to expose a promise on the ConfigService and let the API services use a .then() in the constructor. The main complaint for me is simply that APP_INITIALIZER is only a partial solution since it only holds up component initialization but doesn't know anything about the service side of things. The obvious solution would be to introduce async provider initialization. |
There are structural workarounds but this does seem like a basic feature I would agree with @rehfeldchris that there isn't a "right way" to do this at present and this causes a lot of a confusion. |
Another way to work around this, at least until angular actually support async providers is to inject a value object, which then gets updated using Object.assign(valueObj, serverResponse). It's still hacky, but should at least allow other client services to use a more simple object. Just beware, this doesn't actually solve the problem of ensuring the configs are ready. It just makes the semantics of getting them a bit more elegant. |
For those that don't mind slowing down the bootstrapping process a tiny bit extra, you can do the async requests before bootstrapping. It's messy, but it's a reliable option until we get something better:
and then use a more normal service creation strategy
I'd still rather see framework level support for this. It will make it easy to use, for example, an ApiService to load the config, and it will probably perform better if the framework does it. The above method likely forfeits some browser resource loading / work parallelism by making everything wait for the config so early in the bootstrap processing, before other work might get started. |
A solution to this would also promote real re-usable Angular libraries. We have an Angular 6 Library that is just domain services that I want others in the org to use. Currently, configured like If someone on the Angular team could comment on the priority of this item and if/when it is on their radar to implement, that would be great. |
This feature would be so useful for implementing reusable services in a more natural way only by injecting object-value tokens. I also have one npm package with domain services. These services receive the base url for all the endpoints with a token. This url comes from a fetch to the server. Currently the only way to achieve this is by fetching the url before the angular boot and then creating an extra provider that I pass to the bootstrap function instead of using a global variable. The workaround is to have some setup(config) function in the service that is called from an app_initializer to make use of async factory but I prefer the first option, it's more cleaner. The problem is that you're forcing the consumers to fetch the configuration before Angular. Any news on this? |
I have the same problem. I am trying to provide ConfigService using APP_INITIALIZER and use ConfigService to provide InjectionToken via useFactory, which has dependency on ConfigService. The problem is that inside the useFactory I get the ConfigService but it is not resolved yet. ConfigService should have config object ready to use, which is not true. By the way console.log inside useFactory is executed first and then comes the console.log from inside the APP_INITIALIZER. I want to have finished ConfigService with ready to use config file and then provide InjectionToken via using this ConfigService |
+1 I wanted to add my to support this feature request. Angular has grown a lot since its first release and the entire community has grown as well, patterns has been creating and a lot of idea has been adopted from other successful frameworks as well. Specifically to my case, we are using a 3rd party library with services that expect to receive configuration data as Injection Tokens. We are storing all of our data, particularly configuration on our store and the way to access it is through selectors which returns observable. |
Dealing with JIT is doable even for
Great for JIT. Not so great with AOT. Would love ideas on AOT. Maybe a dynamic export that the AOT plugin can handle? |
Just wanted to voice my +1 for this as well. I'm not presently using Angular, but am using its injection functionality via inject-js. Ideally I'd like to be able to return an observable from my factory provider to register that I can subsequently inject. |
Same here +1, this is deeply needed especially when you have a complex app growing ... |
Use case : A lib provides authentification, which is configured by a provider as : in a multi-tenant application, the fbkeyid-dynamicvalue should be retrieved server side, asynchronously. Happy to have a solution on this, unless that I miss it, there is none ... Of course, considering without modifying the UserModule lib ... Pretty standard use case. By the way, was it not possible in angular 1.X ? |
Is it going to be developed after ivy is ready? |
This is extremely frustating. I beleive my case is similar to @anymos This is the relevant part in my forRoot() providers[]
so "loadedConfig" is returned as a ZoneAwarePromise of sorts, there´s no way to assign the actual value. It´s there, but you can´t assign it. It´s async but I can´t use the result... I see no other option but to use a window object (script src="appsettings.js") , because that WILL be available and it´s the only way i can think of keeping the config separate without too much hassle. I hope they provide a solution for this. |
+1 @jimbarrett33 , I'm having the same issue . Did you find a temporary solution to the problem. Before I go with the workaround of fetching data during bootstap I want to check with you. |
@chiranjeevi-puvvula-ascendlearning-com Still no other solution other than using fetch. The fetch solution is not that bad unless it's a huge configuration and even then...
I ended up fetching only dynamic config required for services (mainly endpoint config stuff) and for those services, which had already been loaded in forRoot(), called an "updateConfig()" method on them in the "initialize" service after the fetch. |
This feature is necessary. NestJS (which is a node.js backend framework yet similar to Angular) supports asynchronous providers[1], and it is a highly praised feature. @kamilmysliwiec Can you help implementing this async-provider-loader for Angular? 😂 |
I'm still struggling with this. I just want to be able to dynamically load an appConfig asynchronously and I can't quite get things to work. Some version of the above solutions get me most of the way there, but my APP_CONFIG token injections get called before it is loaded because the modules load things that need it prior to the process completing -- or some other form of race condition. I've tried many different amalgamations and permutations to try to resolve the races consistently, but nothing works consistently as fetching before the application loads in the main.ts -- something that no longer is supported by PWA applications, so I need another solution. The following is what has worked for me for the past 2 years: main.ts import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { APP_CONFIG } from '@core/constants/app.config';
import { EnvService } from '@core/services/env.service';
import { AppModule } from './app/app.module';
import { IAppConfig } from './app/core/interfaces/app-config.interface';
import { envServiceFactory } from './app/core/services/env.service.provider';
const envService = envServiceFactory(); // This gets the environment from a javascript file
if (envService.production) {
enableProdMode();
}
let appConfig: IAppConfig;
fetch(`./assets/configs/app/app-config.${envService.environmentName}.json`)
.then(async res => {
appConfig = await res.json();
appConfig.version = envService.version;
appConfig.updatedDate = envService.updatedDate;
appConfig.environmentName = envService.environmentName;
appConfig.production = envService.production;
}).catch(err => console.error(`Fatal Error: app-config.${envService.environmentName}.json failed to load: ${err}`))
.then(next => {
platformBrowserDynamic([
{ provide: EnvService, useValue: envService },
{ provide: APP_CONFIG, useValue: appConfig },
]).bootstrapModule(AppModule).then(ref => {
// Ensure Angular destroys itself on hot reloads.
if (window['ngRef']) {
window['ngRef'].destroy();
}
window['ngRef'] = ref;
// Otherwise, log the boot error
}).catch(err => console.error(err));
}).catch(err => console.error(`Fatal Error: Configuration Failed to Load. ${err}`)); I have never successfully accomplished the same thing as above loading it within app.module. The closest I've come is: app.module.ts {
provide: APP_CONFIG,
deps: [EnvService, HttpClient],
useFactory: async (envService: EnvService, http: HttpClient, store: Store): Promise<IAppConfig> => {
return await appConfigServiceFactory(envService, http)
.then((appConfig: IAppConfig) => {
localStorage.setItem('appConfig', JSON.stringify(appConfig));
return appConfig;
});
}
}, app-config.service.ts import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { setAppConfig } from '@core/actions/core.actions';
import { IAppConfig } from '@core/interfaces/app-config.interface';
import { Store } from '@ngrx/store';
import { firstValueFrom } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { EnvService } from './env.service';
@Injectable({
providedIn: 'root'
})
export class AppConfigService {
context = AppConfigService.name;
constructor() { }
async loadConfig(envService: EnvService, http: HttpClient): Promise<IAppConfig> {
console.log(this.context, '> ',
`Loading app-config from environment: ${envService.environmentName}`);
return await firstValueFrom(
http.get(`./assets/configs/app/app-config.${envService.environmentName}.json`)
.pipe(
map((appConfig: IAppConfig) => {
appConfig.version = envService.version;
appConfig.updatedDate = envService.updatedDate;
appConfig.environmentName = envService.environmentName;
appConfig.production = envService.production;
return appConfig;
}),
catchError(error => {
console.log(`Fatal Error: App Config failed to load: ${error}`);
throw new Error(error);
})
)
);
}
}
export const appConfigServiceFactory = async (envService: EnvService, http: HttpClient, store: Store): Promise<IAppConfig> => {
const appConfigService = new AppConfigService();
return await appConfigService.loadConfig(envService, http);
}; But APP_CONFIG is not populated early enough and services loaded from my CoreModule or components that need DI injection of the token need it before it is ready and no matter what I try, I can't seem to get an architecture that makes them wait long enough. This has to be a pretty common thing and it shouldn't be this hard. Any suggestions would be appreciated. |
@maplion I've been using a solution I mentioned above in PWA applications successfully for a while now. See this StackOverflow answer for details. It uses XMLHttpRequest rather than fetch to build the configuration before bootstrapping the Angular module. I agree, it shouldn't be this hard. Glenn. |
@gmaughan This looks promising. I'll try it out, thank you! |
@gmaughan You're the man; your solution works exactly like I need. |
I would LOVE to see this feature! Would open up so many possibilities. Why is this still not a thing? |
Hi guys! Check this repo maybe could bring you an optimal solution... |
Angular's DI system is designed to be synchronous, and we have no plans to make this kind of fundamental change, for a few reasons:
Rendering in Angular today is synchronous. When a component Having asynchronous services would make component rendering asynchronous, because any individual component could not be created without perhaps waiting for a dependency to resolve. The same is true for all DI APIs, such as
If we make rendering asynchronous, not only is it bad for performance, but it limits how the application can surface that it's currently in a loading state. Components may want to render a loading indicator, play animations, and give other signals to the user that they're currently blocked. If the framework is forced to block before it can even instantiate the component, there's no opportunity to customize the behavior - the user just stares at a blank screen or (worse) a half rendered component. Instead, asynchronous dependencies should be modeled within the values injected, not within the DI system itself. That is, you can provide export function provideSafeAsync<T>(
token: T | InjectionToken<T>,
initializer: () => Promise<T>
): Provider[] {
const container: { value?: T } = { value: undefined };
return [
{
provide: APP_INITIALIZER,
useValue: async () => {
container.value = await initializer();
},
multi: true,
},
{
provide: token,
useFactory: () => {
if (!inject(ApplicationInitStatus).done) {
throw new Error(
`Cannot inject ${token} until bootstrap is complete.`
);
}
return container.value;
},
},
];
} |
@alxhub Thank you for your arguments and logical justifications. There are several fundamental ideas and conceptual links in them. But isn't the initial problem most developers come up with when talking about asynchronous providers that they are looking for a way to initialize some setup parameters before module imports are processed? |
From the discussion maybe it would be a good idea to have a documentation on this topic. Or maybe some helper function like shown above with documentation and usage examples. As I didn't manage to do this I've build a wrapper around the bootstrap process and use side effects to store data for initializing the application. But I would like to have some process that is more angular like. |
Completely agree with Alex's assessment. The good news that we've got more hooks now into providers definition, most notably on the route level. See the related discussion in #17606 (comment) |
We definitely need some kind of "silver bullet approach" for this kind of issues. Often we need appsettings in Angular. |
Is there any chance to see a usage example of @alxhub 's Answer? How can I make a HttpClient Call with this approach during startup? |
What if there was a new category of provider, which was only made available to Imagine the following hypothetical: @Component({
standalone: true,
template: `
<div *ngIf="userIsLoggedIn">
Only show if the user is logged in.
</div>
<div *ngIf="userIsAdministrator">
Show expensive stuff that only applies if the user is an admin; Load expensive admin-only components and modules to boot.
</div>
`,
templateProviders: [{
provide: 'userIsLoggedIn',
async: {
defaultValue: false,
useFactory: () => import('get-user-is-logged-in.function').then(m => m.getUserIsLoggedInFunction),
deps: () => [import('user-service').then(m => m.UserService)]
}
},
{
provide: 'userIsAdministrator',
async: {
defaultValue: false,
useFactory: () => import('get-user-is-administrator.function').then(m => m.getUserIsAdministratorFunction),
deps: () => [
import('expensive-and-inefficient-admin-user-service').then(m => m.AdminUserService),
import('another-expensive-dependency-to-demonstrate').then(m => m.AnotherExpensiveDependency)
]
}
}
]
})
export class Foo {} |
there is definitely number of cases where various workaround ar in use instead of providing reasonable solution within Angular. One more example is when you try to combine I did hope that I could eg use |
But you actually can use app_initializer for that |
@Lonli-Lokli so the case is that
|
@ciekawy Yep, but on errorHandler init line all app_initializer deps should be already created, ie even async loaded. |
route is way too late. I can always do typical approach to use
I believe it should be possible to handle async providers (provide resolved values) within |
Route is never late as it creating now before app component |
In my case I need to resolve async value to create |
While APP_INITIALIZER works well for app initial loading, it does not work for lazy loaded modules that also need to initialize their providers long after the app started. Scenarios where provider configuration might not be known at app start or where providers are incrementally setup/loaded later in the app's run. We're on 15th version while still thinking in terms of rigid and statically configured monoliths without async option (not even suggested workaround/example after almost 5 years)? |
This discussion is very much interesting. Several reasons have been mentioned on why Angular itself can't implement async providers by design. However, from an application perspective having the option of declaring an async provider that can be lazily resolved is something that might be needed. That is why I've created a library to solve this problem with an ergonomic API 🎉
There you can find all the documentation, but as an appetizer check this example of the possibilities it offers: // main.ts
bootstrapApplication(ParentComponent, {
providers: [
provideAsync({
provide: MY_SERVICE,
useAsyncClass: () => import('./my-service').then((x) => x.MyService),
}),
],
});
// parent.component.ts
@Component({
template: `<child-component *ngxResolveAsyncProviders></child-component>`,
imports: [ResolveAsyncProvidersDirective, ChildComponent],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class ParentComponent {}
// child.component.ts
@Component({
selector: 'child-component',
template: `Async injector value: {{ service.value }}`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class ChildComponent {
readonly service = inject(MY_SERVICE);
} Async providers are declared through The library is fully featured, internally using an async injector. Providers can be declared with Check this online Stackblitz playground with a live demo. Please give it a try, feedback is welcome! 😄 |
I'm facing the same issue, there are some services initialized before providing APP_INITIALIZER, and I'm also experiencing the issue when using a library mentioned by @jimbarrett33 link . is there any relevant solution ? thanks in advance |
As a 300th "liking" of this issue I would ask is it going to happen? |
It's been 5 years with no traction - don't hold your breath. |
I'm submitting a...
Current behavior
Angular doesn't seem to support providers which asynchronously create their object / service. For those who want to load config from a server dynamically, they seem to be encouraged to use a kludge comprised of creating a
ConfigService
and then usingAPP_INITIALIZER
to delay the bootstrapping just long enough to make an async call and inject the results into theConfigService
. But, this is a hack, and is buggy with race conditions - theConfigService
may potentially be injected into other services, and be used / read from before this bootstrapping happens, causing bugs because the object gets used before it's ready.Here's a plunk which demonstrates the problem: https://plnkr.co/edit/plWAT5i5BFtjastSdaVy?p=preview
Notice the problem is that another service will read from the
ConfigService
before it's truly finished being "constructed" IMO, since we need to construct the object, and then finish initializing it's state theAPP_INITIALIZER
step. I think reading config in a constructor is pretty common programming practice.Expected behavior
I think it would be great to be able to do something like:
(notice the
useAsyncFactory
idea to tell ng that it should wait on the promise, and use the value it produces as the service)I think that's a pretty straight forward way to do it, but whatever you think is the best way to address it.
What is the motivation / use case for changing the behavior?
My use case is to fetch config from a server, and stick it into a service, and then inject this
ConfigService
into other services. I do not want to injectConfigService
into other stuff until it's fully constructed and ready to be used - in other words, until the asynchronous request to the server has completed and myConfigService
has all the values populated into it.I've seen quite a few other users trying to accomplish the same thing on Stackoverflow. This kludge is becoming the defacto way to do it, and SO answers are getting highly upvoted, and multiple articles are being published on other websites - all recommending a bug-prone method. That's not good for the framework's reputation long term. It should be easy to do it "the right way", whatever that is.
Environment
Other issues that would be well served by async providers:
#19448
#20383
The text was updated successfully, but these errors were encountered: