In this article, we are going to discuss feature modules in Angular and lazy load them with the help of one practical example.

What is Angular?
Angular is a popular open-source JavaScript framework for building web applications. It was developed by Google and is currently maintained by the Angular Team at Google. Angular allows developers to create dynamic, single-page applications (SPAs) and provides a structured approach to building complex web applications.

What is a Module?
In programming, a module is a self-contained unit of code that performs a specific task or provides a set of related functions. It is a way of organizing and managing code in a modular and reusable manner.

A module can be a separate file or a collection of files that contain functions, classes, variables, or other code components. It encapsulates a specific set of functionalities, making it easier to understand, test, and maintain code.

Feature Module in Angular

In Angular, a feature module is a way to organize and encapsulate a specific set of features and functionality within an application.

It contains a group of related components, directives, services, and a few other files. It helps us maintain and manage the application codebase.

For example, Suppose we have an online shopping application that contains feature modules like user registration, products, cart, and many more with its services, directives, components, and routing-related configuration.
Benefits of Feature Module

Here are some key benefits of feature modules in Angular:

  • Encapsulation: Feature modules encapsulate related components, directives, services, and other code files within a specific feature or functionality. This encapsulation provides a clear boundary and separation of concerns, making it easier to understand and maintain code.
  • Code Organization: Feature modules help organize your codebase by grouping related code files together. This organization enhances code readability and allows developers to navigate the codebase more efficiently.
  • Code Reusability: Feature modules can be reused across multiple applications or within the same application. This reusability promotes modular and scalable development by allowing you to extract and reuse modules with specific functionalities.
  • Lazy Loading: The lazy loading feature allows you to load feature modules on-demand, improving the initial load time and performance of your application. Lazy loading ensures that only the necessary modules are loaded when navigating to specific routes, reducing the initial bundle size and optimizing the user experience.
  • Dependency Management: Feature modules in Angular have their own set of dependencies and can manage their own imports and exports. This helps in managing dependencies and prevents naming conflicts with other modules in your application.
  • Clear Interfaces: Feature modules define clear interfaces through exports, providing a way to communicate with other parts of the application. By exporting specific components, directives, or services, other modules can make use of them and interact with the features provided by the module.
  • Testing and Debugging: Feature modules enhance testability and debugging capabilities. By encapsulating related code files, it becomes easier to write unit tests specific to a module’s functionalities and isolate any issues or bugs within that module.

Lazy Loading
Lazy loading is a technique in Angular that allows you to load modules asynchronously and on-demand when they are needed. It is a powerful feature that helps improve the initial loading time of your application by loading only the necessary code for the current route instead of loading the entire application upfront.

Feature Module Example

Step 1. Install NodeJS: https://nodejs.org/en/download
Step 2. Install Angular CLI using the following command:

npm install -g @angular/cli

Step 3. Verify NodeJS and Angular CLI are installed or not using the following commands:
node –version
ng version

Step 4. Create a new Angular Application.
ng new angular-modules-demo

Step 5. Open the Angular application in one of the editors, like VS Code, and install the bootstrap with the help of the following command:
npm install bootstrap

next, add the bootstrap script inside the angular.json file inside the scripts and styles section
"styles": [
  "src/styles.css",
  "./node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
  "./node_modules/bootstrap/dist/js/bootstrap.min.js"
]

Step 6. Create the “FirstModule” feature module.
Navigate into the project directory:
cd angular-modules-demo

Next, create the “FirstModule” feature module by running the following command:
ng generate module modules/first-module –routing

This will generate a new module named FirstModule under the modules directory, along with a routing module named first-module-routing.module.ts specific to this module.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { FirstModuleRoutingModule } from './first-module-routing.module';
import { FirstModuleComponent } from './first-module.component';

@NgModule({
  declarations: [
    FirstModuleComponent
  ],
  imports: [
    CommonModule,
    FirstModuleRoutingModule
  ]
})
export class FirstModuleModule { }

Step 7. Create the default component for FirstModule.
Generate the default component for “FirstModule” by running the following command:
ng generate component modules/first-module/first-module-component

This will create a new component named FirstModuletComponent within the first module.
import { Component } from '@angular/core';

@Component({
  selector: 'app-first-module',
  templateUrl: './first-module.component.html',
  styleUrls: ['./first-module.component.css']
})
export class FirstModuleComponent {

}

Step 8. Configure the routing for FirstModule.
Open the first-module-routing.module.ts file under the modules/first-module directory and modify it as follows:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FirstModuleComponent } from './first-module.component';

const routes: Routes = [{ path: '', component: FirstModuleComponent }];

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

Step 9. Create the “SecondModule” feature module

Create the “SecondModule” feature module by running the following command:
ng generate module modules/second-module –routing

This will generate a new module named SecondModule under the modules directory, along with a routing module named second-module-routing.module.ts specific to this module.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { SecondModuleRoutingModule } from './second-module-routing.module';
import { SecondModuleComponent } from './second-module.component';

@NgModule({
  declarations: [
    SecondModuleComponent
  ],
  imports: [
    CommonModule,
    SecondModuleRoutingModule
  ]
})
export class SecondModuleModule { }


Step 10. Create the default component for SecondModule.
Generate the default component for “SecondModule” by running the following command:
ng generate component modules/second-module/second-module-component

This will create a new component named SecondModuleComponent within the second module.
import { Component } from '@angular/core';

@Component({
  selector: 'app-second-module',
  templateUrl: './second-module.component.html',
  styleUrls: ['./second-module.component.css']
})
export class SecondModuleComponent {

}

Step 11. Configure the routing for the second module.
Open the second-module-routing.module.ts file under the modules/second-module directory and modify it as follows:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SecondModuleComponent } from './second-module.component';

const routes: Routes = [{ path: '', component: SecondModuleComponent }];


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


Step 12. Update the App Routing
Open the app-routing.module.ts file under the src/app directory and modify it as follows:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
 { path: 'first-module', loadChildren: () => import('./modules/first-module/first-module.module').then(m => m.FirstModuleModule) },
 { path: 'second-module', loadChildren: () => import('./modules/second-module/second-module.module').then(m => m.SecondModuleModule) },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }


Here we use lazy loading in this example to navigate features modules based on navigations.

To implement lazy loading in Angular, follow these steps:
Open the main app routing module, app-routing.module.ts, and update the routes to use the loadChildren property instead of the component property for the routes that you want to lazy load. For example:
const routes: Routes = [
 { path: 'first-module', loadChildren: () => import('./modules/first-module/first-module.module').then(m => m.FirstModuleModule) },
 { path: 'second-module', loadChildren: () => import('./modules/second-module/second-module.module').then(m => m.SecondModuleModule) },
];


By using lazy loading, you can split your application into smaller, more manageable chunks, improving performance by loading modules only when they are required. This approach is particularly useful for larger applications or when you have sections of your application that are accessed less frequently.

Lazy-loaded modules are loaded asynchronously and cached by the Angular router, so subsequent visits to the same route will load the module from the cache instead of making a new request to the server.

Step 13. Update the app.component.html file.

<button type="button" class="btn btn-primary" routerLink="first-module">First Module</button>
<button type="button" class="btn btn-secondary" routerLink="second-module">Second Module</button>
<router-outlet></router-outlet>

Here we used bootstrap buttons with a router link for navigation purposes and, at the bottom, a router outlet to render different components of the feature module based on navigation.

Step 14. Finally, start the development server by running the following command:
ng serve

Navigate to http://localhost:4200 in your web browser, and you should see the Angular Modules Demo App with two navigation links: “First Module” and “Second Module.” Clicking on each link will lazily load and display the corresponding module’s component.

When we navigate the feature module, only its corresponding files will get loaded because we are lazy to load them with load children, as shown in the above image under the application console. Also, it improves performance when we have a big project with different complex modules.

Step 1. we need an Employee interface to define the properties of the employee objects as below. For this, we add one new file called employee.ts and add the below code there –

export interface EmployeeInfo
{
    emp_id : number;
    emp_code : string;
    emp_name : string;
    emp_mobile : number;
    emp_email : string;
}


Step 2. Now, add a new file called app.employee.service.ts and add the below code –
import { EmployeeInfo } from './employee';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class EmloyeeService {
  public employeeList: EmployeeInfo[] = [];

  ngOnInit(){
    this.employeeList = [
      { "emp_id": 1, "emp_name": "Roger Smith", "emp_code" : "ET0002334", "emp_mobile" : 9830098300, "emp_email": "[email protected]"},
      { "emp_id": 2, "emp_name": "Alex Bob", "emp_code" : "ET0002992", "emp_mobile" : 9830198301, "emp_email" : "[email protected]"},
      { "emp_id": 3, "emp_name": "Stephen Ken", "emp_code" : "ET0001675", "emp_mobile" : 88830098312, "emp_email" : "[email protected]"},
      ];
  }

  getEmployeeList()  {
    if (this.employeeList.length == 0 )
        this.ngOnInit();
    return this.employeeList;
  }

  initializeData(){
    this.getEmployeeList();
  }

  getEmployee(id : number){
    return this.employeeList.find(e=>e.emp_id == id);
  }

  addEmployeeInfo(emloyee : EmployeeInfo){
    this.employeeList.push(emloyee);
  }
}

Step 3. Now, add a new standalone component called app.employee.index.ts and add the below code –
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Router } from '@angular/router';

import { EmployeeInfo } from './employee';
import { EmloyeeService } from './app.employee.service';

@Component({
  selector: 'app-employee-index',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './app.employee.index.html'
})
export class EmployeeIndexComponent implements OnInit {
  public employeeList : EmployeeInfo[] = [] ;

  constructor(
    private _employeeService : EmloyeeService,
    private _route: Router) {

  }

  ngOnInit(){
    this.employeeList = this._employeeService.getEmployeeList();
  }

  navigateUrl(path:string, id:number){
    this._route.navigate([path, id]);
  }
}

Step 4. Now, open the HTML file called app.employee.index.html and add the below code –
<div class="container-fluid">
    <div class="row">

        <h2>Employee Records</h2>
        <div style="position: relative; width: fit-content; float: right;" class="text-right">
            <a class="btn btn-primary" href="/employee-add" role="button">Add Employee</a>
        </div>
        <div class="table-responsive">
            <table class="table table-striped table-sm">
                <thead>
                    <tr>
                        <th scope="col">S.No</th>
                        <th scope="col">Employee Code</th>
                        <th scope="col">Employee Name</th>
                        <th scope="col">Mobile No</th>
                        <th scope="col">Email Id</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    <tr *ngFor='let emp_data of employeeList'>
                        <td>{{emp_data.emp_id}}</td>
                        <td>{{emp_data.emp_code}}</td>
                        <td>{{emp_data.emp_name}}</td>
                        <td>{{emp_data.emp_mobile}}</td>
                        <td>{{emp_data.emp_email}}</td>
                        <td>
                            <button type="button" class="btn btn-info" (click)="navigateUrl('/employee-view', emp_data.emp_id)">Info</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>


Step 5. Now, open the app.component.ts file and import the EmployeeIndexComponent within that component imports array objects.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { provideRouter, RouterOutlet } from '@angular/router';
import { EmployeeIndexComponent } from './employees/app.employee.index';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet, EmployeeIndexComponent],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Angular 16 Application - Standalone App Demo';

}


Routing with Standalone Component
Now, in the above employee index component, we create two buttons – Add Employee and another button for View Employee details. By using these two buttons, we will navigate to the related component with the help of the routes. Now, we will define how we can define routes in the application.

Step 1. Now, add another standalone component called app.employee.view.ts file and add the following code –
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { Router } from '@angular/router';

import { EmployeeInfo } from './employee';
import { EmloyeeService } from './app.employee.service';
import { last } from 'rxjs';

@Component({
  selector: 'app-employee-add',
  standalone: true,
  imports: [CommonModule, FormsModule],
  templateUrl: './app.employee.add.html'
})
export class EmployeeAddComponent implements OnInit {
  public employeeList : EmployeeInfo[] = [] ;
  public employee : any = {};

    constructor(
    private _employeeService : EmloyeeService,
    private _route: Router) {
  }

  ngOnInit(){
    this.employeeList = this._employeeService.getEmployeeList();
  }

  addRecord(){
    var lastNo = this.employeeList[this.employeeList.length-1].emp_id;
    if (lastNo == null)
      lastNo = 0;
    this.employee.emp_id = lastNo +1;
    this._employeeService.addEmployeeInfo(this.employee);
    this.navigateUrl();
  }

  navigateUrl(){
    this._route.navigate(['home']);
  }
}


Step 2. Now, open the app.employee.view.html file and add the following code –
<h2>Add Employee</h2>

<form>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Employee Code</label>
        </div>
        <div class="col-sm-8">
            <input type="text" name="emp_code" [(ngModel)] ="employee.emp_code"/>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Employee Name</label>
        </div>
        <div class="col-sm-8">
            <input type="text" name="emp_name" [(ngModel)] ="employee.emp_name"/>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Email Id</label>
        </div>
        <div class="col-sm-8">
            <input type="email" name="emp_email" [(ngModel)] ="employee.emp_email"/>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Mobile No</label>
        </div>
        <div class="col-sm-8">
            <input type="number" name="emp_mobile" [(ngModel)] ="employee.emp_mobile"/>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">

        </div>
        <div class="col-sm-8">
            <input type="submit" value="Submit" class="btn-primary" (click)="addRecord()"/>
        </div>
    </div>
</form>

<a href="#">Back to Home</a>


Step 3. Now, add another standalone component called app.employee.add.ts file and add the following code –
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Params, Router } from '@angular/router';

import { EmployeeInfo } from './employee';
import { EmloyeeService } from './app.employee.service';

@Component({
  selector: 'app-employee-view',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './app.employee.View.html'
})
export class EmployeeViewComponent implements OnInit {
  public employee : any = {};
  public employeeId : number = 0;

  constructor(private _employeeService : EmloyeeService,
    private router: Router,
    private route: ActivatedRoute) {
  }

  ngOnInit() {
    this.route.params.subscribe((params: Params)=> this.employeeId = params['id']);
    this.employee = this._employeeService.getEmployee(this.employeeId);
  }
}


Step 4. Now, open the app.employee.add.html and add the following code –

<h2>Employee Details</h2>

<div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Employee Id</label>
        </div>
        <div class="col-sm-8">
            <span>{{ employee.emp_id }}</span>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Employee Code</label>
        </div>
        <div class="col-sm-8">
            <span>{{ employee.emp_code }}</span>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Employee Name</label>
        </div>
        <div class="col-sm-8">
            <span>{{ employee.emp_name }}</span>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Email Id</label>
        </div>
        <div class="col-sm-8">
            <span>{{ employee.emp_email }}</span>
        </div>
    </div>
    <div class="row mb-3">
        <div class="col-sm-4">
            <label>Mobile No</label>
        </div>
        <div class="col-sm-8">
            <span>{{ employee.emp_mobile }}</span>
        </div>
    </div>
</div>

<a href="/home">Back to Home</a>


Step 5. So, open the app.routes.ts file and add the below component –
import { Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { EmployeeIndexComponent } from './employees/app.employee.index';
import { EmployeeAddComponent } from './employees/app.employee.add';
import { EmployeeViewComponent } from './employees/app.employee.view';

export const APP_ROUTES: Routes = [
    {
        path: '',
        pathMatch: 'full',
        redirectTo: 'home'
    },
    {
        path: 'home',
        component: EmployeeIndexComponent
    },
    {
        path:'employee-index',
        component:EmployeeIndexComponent
    },
    {
        path:'employee-add',
        component:EmployeeAddComponent
    },
    {
        path:'employee-view/:id',
        component:EmployeeViewComponent
    }
];


Now, we can run the application and test the route functionality.