【问题标题】:Providerfactory for APP_BASE_HREF token is called before APP_INITIALIZER is done在 APP_INITIALIZER 完成之前调用 APP_BASE_HREF 令牌的 Providerfactory
【发布时间】:2018-03-19 12:39:20
【问题描述】:

我刚刚在我们的软件中发现了一个错误 - 很确定它以前可以工作,但现在它坏了。

Plunkr 重现该问题可以在这里找到:https://embed.plnkr.co/BNRcQFWGkkEitxjwE7pi/

在我们的 app.module 中,我们在三个不同的工厂中为 APP_INITIALIZER、APP_BASE_HREF 和 ErrorHandler 设置了提供程序

AppInitializerFactory

为我们的配置调用异步加载方法,该方法存储在 json 文件中。 在此示例中加载 baseHrefsubmitToSentry

错误处理程序

如果配置告诉它,提交给 Sentry 的自定义错误处理程序。 在构造函数中,配置尚未加载 - 这是可以接受的。关于必须先设置 ErrorHandler 或 APP_INITIALIZER 的东西。他们不得不选择一个。相反,一旦发生错误,我们就会查看配置 - 如果我们应该 submitToSentry - 我们会这样做。

BaseHrefFactory

旨在将 APP_BASE_HREF 注入令牌设置为 Config 中的任何 baseHref。 正如 Plunkr 所示,打印的 baseHref 最终未定义 - 不是 config.json 中预期的 myBaseHref

如果...我在 app.module.ts:52 中注释掉 AppRoutingModule - 那么它可以工作。

问题是...(因为我需要 AppRoutingModule)

我想我再次(与错误处理程序一样)处于设置路由和初始化应用程序之间的竞争状态 - 并且在 @angular/router/routerModule 内部的某个地方,它们注入了 APP_BASE_HREF。

有什么方法可以实现 APP_INITIALIZER 阻止路由模块直到完成?

我只是拿错了吗?有没有什么东西可以解决这个问题。

希望有人能够解释我 - 我自己已经用了好几个小时调试它 - 目前迷路了。

很高兴 Plunkr 可以复制它。

【问题讨论】:

    标签: angular


    【解决方案1】:

    [现在我确定为时已晚,但我会尝试回答这个问题。请注意,尽管 plunkr 仍然显示代码,但它实际上并没有运行 - 就像现在 SO 上的大多数 plunkr 示例一样]

    您的提供程序设置有:provide: APP_INITIALIZER, useFactory: AppInitializerFactory, deps: ConfigServiceBase,尽管从函数声明中看不出来(因为您没有明确返回值的类型),AppInitializerFactory 返回 一个返回承诺的函数。这很重要,因为 Angular 初始化过程将等待该承诺得到解决,然后再继续加载应用程序的其余部分(加载初始组件等)。

    您还有provide: APP_BASE_HREF, useFactory: BaseHrefFactory, deps: ConfigServiceBase。但是,由于这APP_INITIALIZER 相关联,Angular 没有特别保证BaseHrefFactory 将在何时执行(尽管在某些要求APP_BASE_HREF 之前它不太可能执行) .它有时AppInitializerFactory之后被执行的事实可能是纯粹的运气。

    需要明确的是,使用 APP_INITIALIZER 令牌的提供程序保证在创建第一个 组件 之前被实例化 - 但这与 服务 的顺序无关被实例化。服务之间的依赖关系通过依赖注入树来表达。

    请注意,Angular 路由器本身使用APP_INITIALIZER - 路由器的一些初始化发生在那里。因此,通过将路由添加到混音中,您可能会更改(已经是随机的)事情发生的顺序。

    要记住的另一件事是,虽然 BaseHrefFactory 依赖于 ConfigServiceBase,但它确实依赖于“ConfigService 的实例,其 load() 方法已被调用”。注入器会很高兴地向它传递一个对 ConfigService 实例的引用,该实例的 load() 方法没有被调用。

    为了解决这一切,我会注入一个服务,而不是尝试注入一个值,如下所示。

    1) 创建一个服务来保存该值:

    @Injectable()
    export class AppBaseHrefService {
      public baseHref: string;
    }
    

    2) 将AppInitializerFactory函数修改为带AppBaseHrefService参数,并添加一个额外的步骤来根据配置设置值:

    export function AppInitializerFactory(config: ConfigServiceBase, appBaseHrefService: AppBaseHrefService) {
    return () => config.load()
        .then(() => {
              appBaseHrefService.baseHref = config.baseHref
        });
    

    }

    3) 让您的 APP_INITIALIZER 提供程序依赖于 AppBaseHrefService

    { provide: APP_INITIALIZER, useFactory: AppInitializerFactory, deps: [ConfigServiceBase, AppBaseHrefService], multi: true }
    

    当然,您可能会认为简单地注入现有的ConfigService / ConfigServiceBase 而不是注入另一个服务更容易。我倾向于同意,除非您的实际 ConfigService 有很多配置信息,而您只想公开其中的一部分。


    更新 2019 年 7 月,因为似乎没有人能够让它工作......

    首先,请记住,我声称能够设置APP_BASE_HREF 提供的值。相反,我建议您可以注入一些 else 来提供您需要的价值。

    我创建了一个StackBlitz 来展示这一点。这里我有以下活动部分:

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

    2. 上面的工厂函数使用ConfigService 异步获取“一些数据”。然后它使用该数据初始化BaseUrlService。可以根据需要在此处添加更多同步或异步操作。 (Async/await 非常适合这些东西)。

    3. 组件(例如本例中的 AppComponent)可以注入 BaseUrlService 并使用它来访问从初始化期间获取的数据派生的基本 URL。由于初始化发生在任何组件创建之前,因此不应出现这些组件看到未初始化服务的情况。

    HTH。

    【讨论】:

    • 嗨,加里,我很担心需要尽快发货,所以在接下来的两周内不会看到这个 - 但一旦我交付了我们的产品,我会尝试恢复当前冲刺。但是感谢您抽出宝贵时间来回答 - 我仍然对解决方案感兴趣,所以我不需要在编译时知道 basehref - 但可以根据他们想要的内部 Web 结构在每个客户站点配置它。目前我的网站需要在所有客户的根目录下运行,因此需要设置单独的域/端口 - 而不仅仅是一个子网站。
    • 嗨,Gary 现在有时间看看。我也无法让您的想法起作用-我认为问题是相同的,无论我依赖什么。 APP_BASE_HREF 是一个特殊变量,无论我如何编写 AppIntializerFactory,我都无法让它等待对 api 的异步调用。 () => load.then() 仍然立即返回 - 并在加载完成之前调用 BaseHrefFactory。
    • 可以确认这个解决方案很遗憾对我也有效; APP_BASE_HREF 对我来说总是第一位的,只看到未解析的值:-(
    • @qqilihq 我添加了一个示例 - 并且还澄清说我不是说您可以更改 APP_BASE_HREF 提供的值,而是提供一种替代方法.
    • @ankhansen:请查看更新,以防它有助于或至少解释我的意思。
    猜你喜欢
    • 2019-12-22
    • 1970-01-01
    • 1970-01-01
    • 2013-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-05
    • 2023-01-30
    相关资源
    最近更新 更多