TL;DR
How can I have a conditional route with conditional import of the corresponding Angular library during build time, depending on a feature flag in the environment file?
Background
I'm having a route configuration that lazy loads an Angular library / module:
{
path: 'wishlist-management',
loadChildren: () => import('@mycompany/thelibrary').then(m => m.TheLibrary),
canActivate: [AuthGuardService],
canLoad: [AuthGuardService],
}
The requirement is, that it should only load the library, if it is enabled with a feature flag in the environments file. For that, I've implemented an AuthGuardService
with the canLoad
method:
public canLoad(
route: Route,
segments: UrlSegment[],
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.isFeatureEnabled(this.router.getCurrentNavigation().extractedUrl.toString());
}
This canLoad
method is also provided in above mentioned route configuration.
However, supposing the feature flag is false and therefore canLoad
returns false
, and that therefore also @mycompany/thelibrary
is not installed as npm dependency in the project, I'm getting an error:
Error: Module not found: Error: Can't resolve '@mycompany/thelibrary' in '/home/user/projects/myproject/frontend/app/node_modules/@mycompany/someotherlibrary/fesm2020'
which is as expected, since canLoad
will only load the chunk file at runtime, but is already building it during build time.
However, how can I achieve a fully conditional route and import of the corresponding library / module already in build time, only if the corresponding feature flag is true? It should only try to import the library if the feature flag is true.
I have found a blog post solving this problem by passing null
in the route configuration and then manipulating the loadChildren
dynamically on router event hooks. But this feels hacky and not the way to go for me.
You have to completely remove any reference to your library from code before build. Use fileReplacements
configuration in angular.json
file. We will replace this file with a dummy based on the build environment.
Create a separate file for loading the route.
src/environments/thelibrary-routes.ts
.
export const thelibraryRoutes = [{
path: 'wishlist-management',
loadChildren: () => import('@mycompany/thelibrary').then(m => m.TheLibrary),
canActivate: [AuthGuardService],
canLoad: [AuthGuardService],
}]
Then in your routes load it as
import { thelibraryRoutes } from 'src/environments/thelibrary-routes.ts'
// Other imports
const routes = [
// Other routes
...thelibraryRoutes
]
Then create a second dummy file.
src/environments/thelibrary-routes.empty.ts
export const thelibraryRoutes = [];
Now this is the cool part. Edit your angular.json file and under build.configurations
add a new one for when your module should be excluded (or if it fits your case, do it in the production
configuration, if you want to exclude your module in production for example). Under your desired configuration, add a new entry to fileReplacements
list like this:
angular.json:
// Other JSON
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
},
{
"replace": "src/environments/thelibrary-routes.ts",
"with": "src/environments/thelibrary-routes.empty.ts"
},
],
// Other JSON
Note that the first entry in fileReplacements
is already there. Only add the second one.
You can use this approach to include/exclude routes depending on your build configuration. Optionally you can set the routes inside the environment.ts
file itself if you don't want to create a new file for the routes, in which case you wouldn't need to edit angular.json
file.