Universal: Guards and Resolvers don't wait for APP_INITIALIZER, if RouterModule initialNavigation: 'enabled' is set

Created on 26 Mar 2020  ·  13Comments  ·  Source: angular/universal

🐞 bug report

Affected Package

@angular/router/RouterModule
@angular/core/APP_INITIALIZER

Is this a regression?

Yes, this used to work all the way up until Angular 8.2.14.
This bug started to appear once I updated to Angular 9 (9.0.7)
EDIT: I updated to Angular 9.1, but the problem persists.

Description

After Upgrading I noticed, that some parts of my application wouldn't start any longer, because the configuration they are depending on is empty.
I used to load this configuration from a config file on my server. I'm using APP_INITIALIZER for this. Upon further investigation I found out, that my guards and resolvers run before my APP_INITIALIZER promise resolves.
Finally I could pin-point this error to app-routing.module.ts. When I uncomment initialNavigation: 'enabled' then the guards and resolvers wait till the APP_INITIALIZER promise resolve.
Once enabled, the guards and resolvers run immediately and don't wait for APP_INITIALIZER.

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
    // initialNavigation: 'enabled'
})
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }

🔬 Minimal Reproduction

Open the console to see when different parts of the application get executed.
MinimalGuard and ResolverService should get executed after AppInitializerService Promise resolved, but unfortunately they get executed before.

https://stackblitz.com/github/felixfiskare/ResolverAndGuardBeforeInit

https://github.com/felixfiskare/ResolverAndGuardBeforeInit

🔥 Exception or Error

guardAndResolverBeforeAppInit

🌍 Your Environment

Angular Version:

 ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 9.0.7
Node: 13.11.0
OS: linux x64

Angular: 9.0.7
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.7
@angular-devkit/build-angular     0.900.7
@angular-devkit/build-optimizer   0.900.7
@angular-devkit/build-webpack     0.900.7
@angular-devkit/core              9.0.7
@angular-devkit/schematics        9.0.7
@ngtools/webpack                  9.0.7
@schematics/angular               9.0.7
@schematics/update                0.900.7
rxjs                              6.5.4
typescript                        3.7.5
webpack                           4.41.2
docs Needs Design

Most helpful comment

@felixfiskare This is caused by the NgUniversal schematics, we added it to avoid flicker when bootstrapping on the client. We'll move this issue over to angular/universal so that we can track a proper resolution. This may be a warning or a prompt, or possibly a link to documentation about certain pitfalls with adding initialNavigation, like the one you encountered here.

All 13 comments

I just found out, that initialNavigation: 'enabled' was added automatically while upgrading from angular 8 to angular 9

I just found out, that initialNavigation: 'enabled' was added automatically while upgrading from angular 8 to angular 9

Can you clarify this in the context of marking this as a regression? I tested the stackblitz demo, going all the way back to Angular 6.0.0 and the behavior remained the same: when initialNavigation is set to 'enabled', the guards and resolvers run before APP_INITIALIZER.

I've looked into the docs and the router_module and determined that guards and resolvers being run before the user's own APP_INITIALIZER is working as intended.

  • The router has its own APP_INITIALIZER.
  • All APP_INITIALIZERS are run in parallel.
  • When using initialNavigation: 'enabled' the router's APP_INITIALIZER triggers a navigation. This means that if your initial navigation depends on your own APP_INITILIZER being completed first, you cannot use initialNavigation: 'enabled'.
  • ApplicationRef.bootstrap (which happens after APP_INITIALIZER completion) calls all APP_BOOTSTRAP_LISTENERs, which triggers initial navigation if using initialNavigation: 'legacy_enabled'
  • All of this also seems to follow the documented behavior for initialNavigation:

'enabled' - The initial navigation starts before the root component is created. The bootstrap is blocked until the initial navigation is complete. This value is required for server-side rendering to work.
'legacy_enabled'- (Default, for compatibility.) The initial navigation starts after the root component has been created. The bootstrap is not blocked until the initial navigation is complete.

That said, if initialNavigation: 'enabled' was added automatically when upgrading as you say, this is a bug in the migration since the initialNavigation options are not equivalent.

I just found out, that initialNavigation: 'enabled' was added automatically while upgrading from angular 8 to angular 9

Can you clarify this in the context of marking this as a regression? I tested the stackblitz demo, going all the way back to Angular 6.0.0 and the behavior remained the same: when initialNavigation is set to 'enabled', the guards and resolvers run before APP_INITIALIZER.

The error appeared after I upgraded to Angular 9. Only later I found out, that 'initialNavigation' was set automatically during the upgrade.

Therefore I agree with you, that this would classify as a migration bug.

I think the value was set, because I'm using SSR.
How am I supposed to have working guards/resolvers AND SSR? The documentation states (as you quoted as well), that 'initialNavigation: enabled' is needed for SSR.

@felixfiskare - Can you update the configuration you're loading to be an Observable such as a ReplaySubject or BehaviorSubject? Whatever depends on those can then read from the Observable instead of attempting to access a value before the value is resolved. If that solution doesn't make sense, can you provide some more details how your app is set up and what depends on the data being retrieved in the APP_INITIALIZER? A new stackblitz to demonstrate the issue would help as well - the current one is just showing execution order, rather than demonstrating the issue you're experiencing.

Thank you for that pointer @atscott and sorry for the delay. It took some time to encapsulate everything in observables, but now everything works!
So I guess, the only thing left to do is to emit a warning when upgrading to angular 9, that initialNavigation: 'enabled' will be set and which consequence this could have.

@felixfiskare This is caused by the NgUniversal schematics, we added it to avoid flicker when bootstrapping on the client. We'll move this issue over to angular/universal so that we can track a proper resolution. This may be a warning or a prompt, or possibly a link to documentation about certain pitfalls with adding initialNavigation, like the one you encountered here.

First phase of this is tracked in #1730, we'll add this section once it's merged.

Hi Guys, is this fix currently merged into v10.1.x ? thanks!

@BruneXX I tried with 10.1.1 and 10.1.2 and the issue still remains

Hi @AceVentura Sad to hear, I think we'll need to wait for this one.. Thanks

Hi, any update of when is this going to be merged? Thanks!

Hi, I'm facing the same issue here too

Was this page helpful?
0 / 5 - 0 ratings