Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Universal requires initialNavigation=enabled which breaks APP_INITIALIZER and Guards

While adding Angular Universal to our app, we had to set the initialNavigation="enabled" flag on the router, in order to avoid flickering.

Now this comes with 2 issues for us:

  1. we create routes dynamically, opening one of those routes now fails
  2. because the app does not wait for the APP_INITIALIZERs to load, routes that are guarded cannot be loaded, because the guard always assumes the user is unauthorized, because the check is happening in the APP_INITIALIZER

I found several Github issues about this (i.e. https://github.com/angular/universal/issues/1623) but none of them really provided a solution.

How can I use initialNavigation="enabled" while at the same time wait for the APP_INITIALIZERs to be executed?

edit (01/02/2021): in Angular 11 the wording has changed and the option is now called enabledBlocking. The issue mentioned here however is not touched by this.

like image 568
tommueller Avatar asked Nov 15 '22 06:11

tommueller


1 Answers

I will provide the solution I found here as well. I also posted it as an issue at Angular Universal's Github repository. If there are changes made to Universal, which will make this easier, I will update this answer.

Solution: Basically what I do now, is to fetch the data about the pages, both in the server and the app, before the Angular app gets bootstrapped at all. Altering the routes-array within app-routing.modules-constructor apparently is early enough to get the dynamic routes in, before the router does its initial navigation.

It looks more or less like this (as stated by Nicolae this can be refactored to avoid the duplicate code):

server.ts:

server.get('*', (req, res) => {
  // fetch dynamic routes
  // /!\ duplicate code to src/main.ts
  fetch('http://static.content/')
    .then(response => response.json())
    .then(resp => {
      const routes = resp.entries.map(route => ({
        path: route.path,
        component: StaticContentComponent,
        data: {
          id: route._id,
          name: route.name
        }
      }));

      res.render(indexHtml, {
        req,
        providers: [
          { provide: APP_BASE_HREF, useValue: req.baseUrl },
          { provide: DYNAMIC_ROUTES, useValue: routes }
        ]
      });
    });
  });

  return server;
}

and basically the same in main.ts:

document.addEventListener('DOMContentLoaded', () => {
  // fetch dynamic routes
  // /!\ duplicate code to server.ts
  fetch('http://static.content/')
    .then(response => response.json())
    .then(resp => {
      const routes = resp.entries.map(route => ({
        path: route.path,
        component: StaticContentComponent,
        data: {
          id: route._id,
          name: route.name
        }
      }));

      platformBrowserDynamic([
        { provide: DYNAMIC_ROUTES, useValue: routes }
      ])
        .bootstrapModule(AppModule)
        .catch(err => console.error(err));
    });
});

And then in my app-routing.module.ts I add the data provided in DYNAMIC_ROUTES to the routes:

const DYNAMIC_ROUTES = new InjectionToken<IEnvironment>('dynamicRoutes');

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      initialNavigation: 'enabled'
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {
  constructor(@Inject(DYNAMIC_ROUTES) private dynamicRoutes, private router: Router) {
    const config = router.config;
    config.unshift(...this.dynamicRoutes);
    this.router.resetConfig(config);
  }
}
like image 129
tommueller Avatar answered Dec 05 '22 18:12

tommueller