guide angular lazy loading - devonfw/devon4ng GitHub Wiki
When the development of an application starts, it just contains a small set of features so the app usually loads fast. However, as new features are added, the overall application size grows up and its loading speed decreases. It is in this context where Lazy loading finds its place. Lazy loading is a design pattern that defers initialization of objects until it is needed, so, for example, users that just access to a website’s home page do not need to have other areas loaded. Angular handles lazy loading through the routing module which redirects to requested pages. Those pages can be loaded at start or on demand.
To explain how lazy loading is implemented using angular, a basic sample app is going to be developed. This app will consist in a window named "level 1" that contains two buttons that redirects to other windows in a "second level". It is a simple example, but useful to understand the relation between angular modules and lazy loading.
This graphic shows that modules acts as gates to access components "inside" them.
Because the objective of this guide is related mainly with logic, the html structure and SCSS styles are less relevant, but the complete code can be found as a sample here.
First write in a console ng new level-app --routing
, to generate a new project called level-app including an app-routing.module.ts file (--routing flag). If you are using Nx, the command would be nx generate @nrwl/angular:app level-app --routing
in your Nx workspace.
In the file app.component.html delete all the content except the router-outlet tag.
<router-outlet></router-outlet>
The next steps consists on creating features modules.
-
run
ng generate module first --routing
to generate a module named first. -
run
ng generate module first/second-left --routing
to generate a module named second-left under first. -
run
ng generate module first/second-right --routing
to generate a module second-right under first. -
run
ng generate component first/first
to generate a component named first inside the module first. -
run
ng generate component first/second-left/content
to generate a component content inside the module second-left. -
run
ng generate component first/second-right/content
to generate a component content inside the module second-right.
ℹ️
|
If you are using Nx, you have to specify the project name (level-app) along with the --project flag. For example, command for generating the first module will be `ng generate module first --project=level-app --routing` |
To move between components we have to configure the routes used:
In app-routing.module.ts add a path 'first' to FirstComponent
and a redirection from '' to 'first'.
...
import { FirstComponent } from './first/first/first.component';
const routes: Routes = [
{
path: 'first',
component: FirstComponent
},
{
path: '',
redirectTo: 'first',
pathMatch: 'full',
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
In app.module.ts import the module which includes FirstComponent
.
....
import { FirstModule } from './first/first.module';
@NgModule({
...
imports: [
....
FirstModule
],
...
})
export class AppModule { }
In first-routing.module.ts add routes that direct to the content of SecondRightModule
and SecondLeftModule
. The content of both modules have the same name so, in order to avoid conflicts the name of the components are going to be changed using as ( original-name as new-name).
...
import { ContentComponent as ContentLeft} from './second-left/content/content.component';
import { ContentComponent as ContentRight} from './second-right/content/content.component';
import { FirstComponent } from './first/first.component';
const routes: Routes = [
{
path: '',
component: FirstComponent
},
{
path: 'first/second-left',
component: ContentLeft
},
{
path: 'first/second-right',
component: ContentRight
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FirstRoutingModule { }
In first.module.ts import SecondLeftModule
and SecondRightModule
.
...
import { SecondLeftModule } from './second-left/second-left.module';
import { SecondRightModule } from './second-right/second-right.module';
@NgModule({
...
imports: [
...
SecondLeftModule,
SecondRightModule,
]
})
export class FirstModule { }
Using the current configuration, we have a project that loads all the modules in a eager way. Run ng serve
(with --project=level-app
in an Nx workspace) to see what happens.
First, during the compilation we can see that just a main file is built.
If we go to http://localhost:4200/first
and open developer options (F12 on Chrome), it is found that a document named "first" is loaded.
If we click on [Go to right module] a second level module opens, but there is no 'second-right' document.
But, typing the URL directly will load 'second-right' but no 'first', even if we click on [Go back]
Modifying an angular application to load its modules lazily is easy, you have to change the routing configuration of the desired module (for example FirstModule
).
const routes: Routes = [
{
path: 'first',
loadChildren: () => import('./first/first.module').then(m => m.FirstModule),
},
{
path: '',
redirectTo: 'first',
pathMatch: 'full',
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
Notice that instead of loading a component, you dynamically import it in a loadChildren
attribute because modules acts as gates to access components "inside" them. Updating the app to load lazily has four consequences:
-
No component attribute.
-
No import of
FirstComponent
. -
FirstModule
import has to be removed from the imports array at app.module.ts. -
Change of context.
If we check first-routing.module.ts again, we can see that the path for ContentLeft
and ContentRight
is set to 'first/second-left' and 'first/second-right' respectively, so writing http://localhost:4200/first/second-left
will redirect us to ContentLeft
. However, after loading a module with loadChildren
setting the path to 'second-left' and 'second-right' is enough because it acquires the context set by AppRoutingModule
.
const routes: Routes = [
{
path: '',
component: FirstComponent
},
{
path: 'second-left',
component: ContentLeft
},
{
path: 'second-right',
component: ContentRight
}
];
If we go to 'first' then FirstModule
is situated in '/first' but also its children ContentLeft
and ContentRight
, so it is not necessary to write in their path 'first/second-left' and 'first/second-right', because that will situate the components on 'first/first/second-left' and 'first/first/second-right'.
When we compile an app with lazy loaded modules, files containing them will be generated
And if we go to developer tools → network, we can find those modules loaded (if they are needed).
To load the component ContentComponent
of SecondLeftModule
lazily, we have to load SecondLeftModule
as a children of FirstModule
:
-
Change component to
loadChildren
and referenceSecondLeftModule
.
const routes: Routes = [
{
path: '',
component: FirstComponent
},
{
path: 'second-left',
loadChildren: () => import('./second-left/second-left.module').then(m => m.SecondLeftModule),
},
{
path: 'second-right',
component: ContentRight
}
];
-
Remove
SecondLeftModule
at first.component.ts -
Route the components inside
SecondLeftModule
. Without this step nothing would be displayed.
...
import { ContentComponent } from './content/content.component';
const routes: Routes = [
{
path: '',
component: ContentComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SecondLeftRoutingModule { }
-
run
ng serve
to generate files containing the lazy modules.
Clicking on [Go to left module] triggers the load of SecondLeftModule
.