在APP_INITIALIZER完成之前,会调用APP_BASE_HREF令牌的Providerfactory [英] Providerfactory for APP_BASE_HREF token is called before APP_INITIALIZER is done
问题描述
我刚刚在我们的软件中发现了一个错误-可以肯定它曾经可以工作,但是现在已经坏了.
I just discovered a bug in our software - pretty sure it used to work, but now it's broken.
可在此处找到重现该问题的插件: https://embed.plnkr.co/BNRcQFWGkkEitxjwE7pi/
Plunkr reproducing the issue can be found here: https://embed.plnkr.co/BNRcQFWGkkEitxjwE7pi/
在我们的app.module中,我们在三个不同的工厂中为APP_INITIALIZER,APP_BASE_HREF和ErrorHandler设置了提供程序
In our app.module we have set up providers for APP_INITIALIZER, APP_BASE_HREF and ErrorHandler in three different factories
AppInitializerFactory
为我们的配置调用异步加载方法,该方法存储在json文件中.在此示例中,加载 baseHref 和 submitToSentry
Calls an async load method for our config, which is stored in a json file. In this sample loads baseHref and submitToSentry
ErrorHandler
一个自定义的错误处理程序,如果config告诉它,它会提交给Sentry.在构造函数中,配置尚未加载-这是可以接受的.关于必须先设置ErrorHandler或APP_INITIALIZER的信息.他们必须选一个.相反,我们会在发生错误时查看配置-如果我们应提交SubmitToSentry-我们会这样做.
A custom errorhandler that submits to Sentry, if config tells it to. In constructor config is not loaded yet - and that is acceptable. Something about having to setup ErrorHandler or APP_INITIALIZER first. They had to pick one. Instead we look at config once an error occurs - and if we should submitToSentry - we do.
BaseHrefFactory
旨在将APP_BASE_HREF注入令牌设置为Config中的baseHref.如Plunkr所示,打印的baseHref最终是未定义的-并非config.json中期望的 myBaseHref .
Was intended to set the APP_BASE_HREF injection token to whatever baseHref in Config was. As the Plunkr shows, baseHref printed ends up being undefined - not myBaseHref as expected from config.json.
如果...我在app.module.ts:52中注释掉了AppRoutingModule-那么它可以工作.
If... I comment out the AppRoutingModule in app.module.ts:52 - then it works.
问题是...(因为我需要AppRoutingModule)
我想我又一次(与错误处理程序一样)处于设置路由和初始化应用之间的竞争状态-在@ angular/router/routerModule内部的某个地方,他们注入了APP_BASE_HREF.
I guess I'm once again (as with the errorhandler) is in a race condition between setting up routing and initializing the app - And somewhere internally in @angular/router/routerModule they inject APP_BASE_HREF.
是否有任何方法可以使APP_INITIALIZER保留路由模块直到完成?
Are there any way to achieve that APP_INITIALIZER holds back routing module until done?
我只是认为错误吗?订单中有什么东西或类似的东西可以解决这个问题.
Am I just holding it wrong? Is there something in the ordering or similar that can fix this.
希望有人能够向我解释-我已经花了几个小时自行调试-现在迷失了.
Hoping for someone to be able to explain me - have used quite a few hours debugging it myself - and is currently lost.
高兴的是,Plunkr可以重现它.
Glad though that the Plunkr could reproduce it.
推荐答案
[现在确定,为时已晚,但是我可以回答这个问题.请注意,尽管plunkr仍然显示代码,但实际上并没有运行-就像当今SO上大多数plunkr示例一样]
您的提供程序设置具有:提供:APP_INITIALIZER,useFactory:AppInitializerFactory,deps:ConfigServiceBase
,尽管从函数声明中并不清楚(因为您不清楚返回值的类型)), AppInitializerFactory
返回一个返回诺言的函数.这很重要,因为Angular初始化过程将等待该承诺得到解决,然后再继续加载其余的应用程序(加载初始组件等).
Your provider setup has: provide: APP_INITIALIZER, useFactory: AppInitializerFactory, deps: ConfigServiceBase
and although it's not obvious from the function declaration (since you're not being explicit about the type of the return value), AppInitializerFactory
returns a function that returns a promise. This is important, since the Angular initialization process will wait for that promise to be resolved before continuing to load the rest of the app (loading the initial component, etc).
您还具有,提供:APP_BASE_HREF,useFactory:BaseHrefFactory,deps:ConfigServiceBase
.但是,由于这不是与 APP_INITIALIZER
相关联的 ,因此Angular对于何时执行 BaseHrefFactory
并没有特别的保证(尽管不太可能执行,直到有人要求提供 APP_BASE_HREF
为止.有时在 AppInitializerFactory
之后执行的事实可能很幸运.
You also have provide: APP_BASE_HREF, useFactory: BaseHrefFactory, deps: ConfigServiceBase
. But, since this is not associated with APP_INITIALIZER
, Angular makes no particular guarantee as to when the BaseHrefFactory
will be executed (though it's unlikely to be executed until something asks for the APP_BASE_HREF
). The fact that it sometimes gets executed after AppInitializerFactory
may be pure luck.
要清楚,确保使用 APP_INITIALIZER
令牌的提供程序在创建第一个 component 之前被实例化-但这与的顺序无关服务被实例化.服务之间的依赖性通过依赖性注入树来表示.
To be clear, providers using the APP_INITIALIZER
token are guaranteed to be instantiated before the first component is created - but this is unrelated to the order in which services are instantiated. The dependency between services is expressed via the dependency-injection tree.
请注意,Angular路由器本身使用了 APP_INITIALIZER
-路由器的某些初始化在此处进行.因此,有可能通过将路由添加到混合中来更改事情发生的顺序(已经是随机的).
Note that the Angular router makes use of APP_INITIALIZER
itself - some of the initialization of the router happens there. So, it's possible that by adding routing into the mix you change the (already random) order in which things happen.
要记住的另一件事是,尽管 BaseHrefFactory
对 ConfigServiceBase
有依赖性,但 not 对"an已调用 load()
方法的 ConfigService
实例".注入器将很高兴将其引用传递给 ConfigService
的实例,该实例的 load()
方法未调用 .
Another thing to bear in mind is that, although BaseHrefFactory
has a dependency on ConfigServiceBase
, it does not have a dependency on "an instance of ConfigService
whose load()
method has been called". The injector will quite happily pass it a reference to an instance of ConfigService
whose load()
method has not been called.
要解决所有这些问题,我将注入 service ,而不是尝试注入值,如下所示.
To solve all this, I would inject a service rather than try to inject a value, as follows.
1)创建一个保存值的服务:
1) Create a service to hold the value:
@Injectable()
export class AppBaseHrefService {
public baseHref: string;
}
2)修改 AppInitializerFactory
函数以使用 AppBaseHrefService
参数,并添加一个额外的步骤以根据配置设置值:
2) Modify the AppInitializerFactory
function to take an AppBaseHrefService
parameter, and add an extra step to set the value based on the config:
export function AppInitializerFactory(config: ConfigServiceBase, appBaseHrefService: AppBaseHrefService) {
return () => config.load()
.then(() => {
appBaseHrefService.baseHref = config.baseHref
});
}
3)为您的 APP_INITIALIZER
提供程序提供对 AppBaseHrefService
的依赖:
3) Give your APP_INITIALIZER
provider a dependency on AppBaseHrefService
:
{ provide: APP_INITIALIZER, useFactory: AppInitializerFactory, deps: [ConfigServiceBase, AppBaseHrefService], multi: true }
当然,您可能会决定,简单地注入现有的 ConfigService
/ ConfigServiceBase
比注入其他服务更容易.我倾向于同意,除非您的实际 ConfigService
具有大量的配置信息,而您只想公开其中的一些信息.
Of course, you may decide that it's easier to simply inject the existing ConfigService
/ ConfigServiceBase
rather than injecting yet another service. I'd be inclined to agree, unless your actual ConfigService
has lots of configuration information and you only want to expose some of it.
更新,2019年7月,因为似乎没人能使它正常工作...
UPDATE July 2019, since no-one seems to be able to get this to work...
首先,请记住,我不是 声称能够设置 APP_BASE_HREF
提供的值.相反,我建议您注入 else 来提供所需的价值.
Firstly, please bear in mind that I'm not claiming to be able to set the value provided by APP_BASE_HREF
. Instead, I'm suggesting that you could inject something else that will provide the value you need.
我已经创建了一个 StackBlitz 来显示此操作.这里有以下几个活动部分:
I've created a StackBlitz that shows this in action. Here I have the following moving parts:
-
我有一个
InitializerProvider
提供程序,该提供程序在AppModule
中定义,并与APP_INITIALIZER
DI令牌相关联.这使用工厂函数进行异步初始化.因为该函数返回了Promise
,所以在解决该诺言(即完成初始化)之前,将不会创建任何组件.
I have an
InitializerProvider
provider defined in theAppModule
and associated with theAPP_INITIALIZER
DI token. This uses a factory function to do asyncronous initialization. Because that function returns aPromise
, no components will be created etc. until that promise has been resolved (i.e. initialization is complete).
上面的factory函数使用 ConfigService
异步获取某些数据".然后,它将使用该数据来初始化 BaseUrlService
.如有必要,可以在此处添加更多同步或异步操作.(异步/等待对于此功能非常有用).
The factory function above uses the ConfigService
to asynchronously fetch "some data". It then uses that data to initialize the BaseUrlService
. More synchronous or asynchronous actions could be added here as necessary. (Async/await is great for this stuff).
组件(例如本示例中的 AppComponent
)可以注入 BaseUrlService
并使用它来访问从初始化期间获取的数据派生的基本URL.由于初始化是在创建任何组件之前进行的,因此在任何情况下都不应使这些组件看到未初始化的服务.
Components (e.g. AppComponent
in this example) can inject BaseUrlService
and use that to access the base URL that's derived from the data fetched during initialization. Since the initialization happens before any components are created, there should be no circumstances in which those components see an un-initialized service.
HTH.
这篇关于在APP_INITIALIZER完成之前,会调用APP_BASE_HREF令牌的Providerfactory的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!