Yannick Jorissen
Yannick Jorissen

Reputation: 51

Dynamically determine loadChildren module type from service

In an Angular app, I would like to determine what module to load for a specified path. It's not about lazy loading. It's about dynamic route configuration/determination.

I have an app which have 3 kinds of access. Each access is in his own module. On app load, I would like to determine which module to load. At the root route level and the child level.

It seems to me that Angular is very flexible for many things but the routing configuration is very static...

Example:

// routed module
const routes: Routes = [
  { path: '', loadChildren: () => getModuleType() }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
class ParentModule {}

// My problem is there, where can I put this function?
getModuleType() {

  // How can I have the dependency injected?
  return someApiService.getProfile().map(profile => {

    if (profile === 'A') {
      return ProfileAModule;
    }

    if (profile === 'B') {
      return ProfileBModule;
    }

  });
}

// some feature module
@NgModule({})
class ProfileAModule {}

@NgModule({})
class ProfileBModule {}

Upvotes: 5

Views: 2010

Answers (1)

Solofeed
Solofeed

Reputation: 151

I would use guarded features module to achieve this.

Example of folder structure for Feature X folder and Core folder containing the guard (You can have N features if you want) :

/core
    /guard
        / feature-x.guard.ts
/feature-x
    /feature-x.router.ts
    /feature-x.module.ts
    /feature-x.component.ts
    /feature-x-home
         /feature-x-home.module.ts
         /feature-x-home.router.ts
         /feature-x-home.component.ts
         /feature-x-home.component.html
         /feature-x-home.component.scss
    /feature-x-route1
         /feature-x-route1.module.ts
         /feature-x-route1.router.ts
         /feature-x-route1.component.ts
         /feature-x-route1.component.html
         /feature-x-route1.component.scss

Feature X

Each feature contains its restricted routes. feature-x.router.ts would be like this :

const featureXRoutes: Route[] = [
  {
    path: '',
    component: RouteXFeatureComponent,
    canActivate: [ FeatureXGuard ],
    children: [
      { path: '', redirectTo: '/feature-x-home', pathMatch: 'full' },
      { path: 'feature-x-home', loadChildren: '@app/feature-x/feature-x-home/feature-x-home.module#FeatureXHomeModule' },
      { path: 'feature-x-route-1', loadChildren: '@app/feature-x/feature-x-route-1/feature-x-route-1.module#FeatureXRoute1Module' },
      // ... etc
    ]
  }
];

export const FeatureXRoutingModule: ModuleWithProviders = RouterModule.forChild(featureXRoutes);

@app is an alias for your source path folder. If you don't have one, use relative path instead. Lazy-loading is optional but i strongly recommend using it.

feature-x-home.router.ts :

const featureXHomeRoutes: Route[] = [{
    path: '',
    component: FeatureXHomeComponent
}];

export const featureXHomeRoutes: ModuleWithProviders = RouterModule.forChild(featureXHomeRoutes);

feature-x.module.ts:

@NgModule({
  imports: [
    FeatureXRoutingModule
  ],
  declarations: [
    FeatureXComponent
  ]
})
export class FeatureXModule {}

feature-x.component.ts:

@Component({
  selector: 'app-feature-x',
  template: '<router-outlet></router-outlet>',
  styleUrls: [ './feature-x.component.scss' ]
})
export class FeatureXComponent {}

You can provide any layout relative to the current feature.

Main module

app.module.ts:

@NgModule({
  imports: [
    CoreModule, // contains all guards declaration

    Feature1Module, // feature-x with x = 1
    Feature2Module, 
    Feature3Module,
    //... etc
    FeatureNModule,

    AppRoutingModule
  ],
  declarations: [
    AppComponent
  ],
  bootstrap: [ AppComponent ]

})
export class AppModule {}

If you don't have a CoreModule then declare all the guards here with providers property.

app.router.ts:

const appRoutes: Route[] = [
  { path: '', redirectTo: '/defaultRoute', pathMatch: 'full' },
  { path: '**', redirectTo: '/defaultRoute' }
];

export const AppRoutingModule: ModuleWithProviders = RouterModule.forRoot(appRoutes);

feature-x.module.ts:

const FEATURE_X = 'YOUR_FEATURE_X';
@Injectable()
export class FeatureXGuard implements CanActivate {
  constructor(
    private profilService: ProfilService,
    private router: Router
  ) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    const canGo = this.profileService.hasProfil(FEATURE_X);

    if (!canGo) {
      this._router.navigate([ this.profileService.getDefaultRouteFor(FEATURE_X) ]);
    }

    return canGo;
  }
}

With this system, depending on his profile, an user will be redirected on the correct "home" page. If he tries to access a page for which he doesn't have the right profile, he will be redirected to his "home" page

Update

You need to use APP_BOOTSTRAP_LISTENER hook for registering app routes depending on your user profile.

https://stackblitz.com/edit/angular-vjfne6

Upvotes: 1

Related Questions