manfredsteyer / module-federation-with-angular-dynamic

Dynamic Module Federation with Angular
119 stars 66 forks source link

Entering the app with dynamically lazy loaded route results in error #15

Open davorz opened 3 years ago

davorz commented 3 years ago

After the user clicks on the header link and the route is loaded (everything still works), if page refresh occurs, the router will report an error: Cannot match any routes. URL Segment: 'flights/flights-search'. Since this route belongs to a dynamically lazy-loaded module, the router does not know about this route before routes are loaded with lookup service and registered, which occurs afterwards when shell app AppComponent is initiated (OnInit).

This occurs only if route config is injected at runtime, however, if routes for lazy loaded modules are registered within the shell module everything works fine, they are ready from the start and the router knows where to look. This approach is fine but eliminates the possibility of keeping the micro frontend configuration separate and requires shell app deployment when new micro frontends are added.

EDIT One of the possible solutions is to register lazy routes in the shell app before AppCompnent initializes, and before the router starts to process routes, using APP_INITIALIZER DI token. This way, if the APP_INITIALIZER function returns a Promise, initialization does not complete until the Promise is resolved. In this case, the config is loaded from the LookupService and registered to the router.

For the purpose of this POC, I have created a simple ConfigService that will provide APP_INITIALIZER with init(): Promise<void> method.

init(): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    this.lookupService.lookup().then((microfrontends) => {
      const routes = buildRoutes(microfrontends, APP_ROUTES);
      this.router.resetConfig(routes);
      resolve();
    });
  });
}
export function buildRoutes(options: Microfrontend[], appRoutes: Routes): Routes {
    const lazyRoutes: Routes = options.map(o => ({
        path: o.routePath,
        loadChildren: () => loadRemoteModule(o).then(m => m[o.ngModuleName])
    }));
    return [...lazyRoutes, ...appRoutes];
}

and then, AppModule can provide APP_INITIALIZER with useFactory and call ConfigService to deal with registering routes from lookupService (or directly by reading config.json file etc.).

providers: [
  ConfigService,
  { 
    provide: APP_INITIALIZER,
    useFactory: initializeConfig, 
    deps: [ConfigService],
    multi: true
  }
]
export function initializeConfig(configService: ConfigService) {
  return (): Promise<any> => { 
    return configService.init();
  }
}
BrusJan commented 2 years ago

Hello davorz, we have encountered similar issue and the solution from your EDIT part of the post does not seem to work for us. Even when I try to take the module-federation-with-angular-dynamic project and add a simple APP_INITIALIZER into mfe2 like this (\mfe2\src\app\app.module.ts):

function initializeApp(): Promise<any> {
  return new Promise((resolve, reject) => {
    console.info('MFE APP INIT');
    resolve(true);
  });
}
...
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: () => initializeApp,
      multi: true
    }
  ],
...

I can see the console info MFE APP INIT only when i go to the mfe port directly, When opening shell app, I see only "Error: Cannot match any routes. URL Segment: 'bookings/bookings-search'" (which is the same error as yours except in the other mfe).

davorz commented 2 years ago

@BrusJan are you loading routes in the Shell app? Because Shell app needs to know routes of all mfe's before routing kicks in.

BrusJan commented 2 years ago

@davorz The code i have provided in my comment is the only thing i added to the original repo. I don't understand how APP_INITIALIZER can cause routing issues. Do you?

ManuelTS commented 2 years ago

+1, having the same issue here as @davorz

richajain1785 commented 2 years ago

Besides having APP_INITIALIZER as given above, routes declaration needs to be removed from app.module.ts from shell.

Just replace RouterModule.forRoot(APP_ROUTES) with RouterModule.forRoot([])