All examples and code references refer to Angular 16.0.0.
In the past, we used the forRoot
or forChild
functions to initialize modules with specific providers or wrote some custom static factory functions to add configurability beyond providers. With Angular’s new standalone API and the elimination of NgModules
, configuring lazy-loaded components or routes became much more straightforward.
Lazy Loaded Components
In our example, we have a simple configuration object SomeConfig
that we want to provide under a specific injection token COMPONENT_CONFIG
. We also export a default configuration for our eagerly loaded components.
// config.model.ts
export interface SomeConfig {
debug: boolean;
text: string;
}
export const COMPONENT_CONFIG = new InjectionToken('Component Config');
export const DEFAULT_CONFIG: SomeConfig = { debug: false, text: 'Eager Component' };
To provide the default configuration with the COMPONENT_CONFIG
token, we need to provide it at the application level. Within Angular’s new standalone project structure, we do it like this:
//app.config.ts
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), {provide: COMPONENT_CONFIG, useValue:DEFAULT_CONFIG}]
};
//main.ts
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
We now create an eagerly loaded HomeComponent
, assign a route, and start our application. Everything works as expected, and the default configuration gets displayed.
So far, so good. Let’s get some laziness in there, shall we?
To do so we create a copy of our HomeComponent
and call it LazyHomeComponent
. In the past, we achieved Iazy-loading like this.
const routes: Routes = [
{
path: 'lazy',
loadChildren: () =>
import('../app/lazy-loaded-configurable/lazy-loaded-configurable.module').then(
(m) => m.LazyLoadedConfigurableModule),
},
];
We called loadChildren
and imported our lazy module. Now without modules, we call loadComponent
instead. It works like loadChildren
worked before, with the difference that we now return a component instead of a module. Now we can lazy load our LazyHomeComponent
. But how do we provide the configuration? Angular got our back. Angular 14 already introduced the possibility of applying a providers array to the Route
definition. That means Angular creates a new EnvironmentInjector
for this route. We can define and access specific services (or configurations) for that route level and all its children. Let’s add the provider’s array to the route configuration for the final result.
//app.routes.ts
export const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'lazy',
loadComponent: () => import('./lazy-home/lazy-home.component'),
providers: [{ provide: COMPONENT_CONFIG, useValue: LAZY_CONFIG }],
}
];
Et voila. We successfully provided our custom lazy-configuration to the lazy-loaded component.
You might have noticed that the component import above has no .then(c => c.LazyHomeComponent)
clause. You can save this part if you add a default export at the end of the components file. So in our case, export default LazyHomeComponent
. The same applies to files containing routes.
Lazy Loaded Routes
But it is not always the case that we want to configure each lazy-loaded component individually. Sometimes we want that good old module-like behavior. One configuration for one route segment and all its child routes/components. No problem. Let’s create a new folder with two more lazy components and define some route structure inside another file.
//more-lazy.routes.ts
export const MORE_LAZINESS_CONFIG: SomeConfig = { debug: false, text: 'More Laziness' };
export const MORE_LAZINESS_ROUTES: Routes = [
{
path: '',
pathMatch: 'prefix',
providers: [{ provide: COMPONENT_CONFIG, useValue: MORE_LAZINESS_CONFIG }],
children: [
{ path: 'lazy-1', component: LazyOneComponent },
{ path: 'lazy-2', component: LazyTwoComponent },
],
},
];
Like in the old module days, we provide an empty base path and define two child routes for our components. To provide the configuration we use the same approach as in our lazy component example above and add some providers array with our configuration.
Now we need to lazy-load these routes. Let’s see how.
export const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'lazy',
loadComponent: () => import('./lazy-home/lazy-home.component'),
providers: [{ provide: COMPONENT_CONFIG, useValue: LAZY_CONFIG }],
},
{
path: 'more-laziness',
loadChildren: () => import('./more-laziness/more-lazy.routes'),
},
];
We see that the good old loadChildren
function is still there but it works slightly different. Istead of a module return value it now expects a Routes
object. So we just need to reference our “bundled” child routes.
And after we start our application we can see that everything works nice and smooth.
Conclusion
Above, we explored configuring and lazily loading Angular components and routes and saw that it got even more accessible. The standalone approach and the possibility to provide on a route level are great addition to the framework to maintain reusable architecture combined with a pleasant developer experience.
GitHub Repository
You can find a sample implementation of the above concept right in this GitHub Repository:
https://github.com/thinktecture/blog-configure-lazy-modules-demo