在APP_INITIALIZER完成之前,会调用APP_BASE_HREF令牌的Providerfactory [英] Providerfactory for APP_BASE_HREF token is called before APP_INITIALIZER is done

查看:61
本文介绍了在APP_INITIALIZER完成之前,会调用APP_BASE_HREF令牌的Providerfactory的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚在我们的软件中发现了一个错误-可以肯定它曾经可以工作,但是现在已经坏了.

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:

  1. 我有一个 InitializerProvider 提供程序,该提供程序在 AppModule 中定义,并与 APP_INITIALIZER DI令牌相关联.这使用工厂函数进行异步初始化.因为该函数返回了 Promise ,所以在解决该诺言(即完成初始化)之前,将不会创建任何组件.

  1. I have an InitializerProvider provider defined in the AppModule and associated with the APP_INITIALIZER DI token. This uses a factory function to do asyncronous initialization. Because that function returns a Promise, 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆