In Angular, modular architecture is key to building scalable, maintainable applications. Modules allow you to group related components, directives, pipes, and services together. Understanding how to import and export modules is essential for reusability and efficient code management. This post will explain how to import modules into other modules, export components, directives, and pipes for use across modules, and provide a practical example of sharing a custom pipe.
Understanding Angular Modules
Angular modules, also known as NgModules, are containers for a cohesive block of functionality. Each Angular application has at least one module, the AppModule
. Additional modules are typically created for feature organization, shared utilities, and routing.
A module is defined using the @NgModule
decorator, which takes metadata such as:
declarations
: Components, directives, and pipes that belong to the module.imports
: Other modules that are needed by this module.exports
: Components, directives, and pipes that are exposed for use in other modules.providers
: Services available to the injector for dependency injection.bootstrap
: The main application view or root component.
Example of a basic module:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MyComponent } from './my-component/my-component.component';
@NgModule({
declarations: [MyComponent],
imports: [CommonModule],
exports: [MyComponent]
})
export class MyModule { }
In this example, MyComponent
is declared in MyModule
and also exported so other modules can use it.
Importing Modules into Other Modules
Angular allows you to import one module into another using the imports
array in the @NgModule
decorator. This is essential for reusing components, directives, and pipes defined in other modules.
The general syntax is:
@NgModule({
imports: [
SomeModule,
AnotherModule
]
})
export class FeatureModule { }
How Importing Works
When you import a module:
- Angular makes all exported declarations (components, directives, pipes) of the imported module available in the current module.
- Services provided in the imported module (if provided in the root or module-level injector) are accessible.
- Modules imported into a feature module do not automatically become global; you must import them wherever needed.
Example: Importing a Shared Module
Suppose you have a SharedModule
containing a custom pipe and a component:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomPipe } from './pipes/custom.pipe';
import { SharedComponent } from './components/shared.component';
@NgModule({
declarations: [CustomPipe, SharedComponent],
imports: [CommonModule],
exports: [CustomPipe, SharedComponent]
})
export class SharedModule { }
To use the CustomPipe
and SharedComponent
in a feature module:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FeatureComponent } from './feature.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [FeatureComponent],
imports: [CommonModule, SharedModule]
})
export class FeatureModule { }
Now, FeatureComponent
can use CustomPipe
in its template:
<p>{{ 'Angular' | custom }}</p>
<app-shared-component></app-shared-component>
Exporting Components, Directives, and Pipes
Exporting is what makes components, directives, and pipes available for use in other modules. Only declarations included in the exports
array of an NgModule can be used by importing modules.
Rules for Exporting
- Only declarations (components, directives, pipes) can be exported.
- Services cannot be exported using the
exports
array; they are made available usingproviders
. - Modules themselves can be exported to make all their exported declarations available.
Syntax
@NgModule({
declarations: [CustomPipe, CustomDirective, CustomComponent],
exports: [CustomPipe, CustomDirective, CustomComponent]
})
export class SharedModule { }
This module can now be imported elsewhere to access all its exported declarations.
Practical Example: Sharing a Custom Pipe Across Modules
Creating a custom pipe and sharing it across modules is a common use case in Angular.
Step 1: Create the Pipe
Generate a pipe using Angular CLI:
ng generate pipe pipes/custom
Implement the pipe:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'custom'
})
export class CustomPipe implements PipeTransform {
transform(value: string, ...args: any[]): string {
return value.toUpperCase();
}
}
This simple pipe converts text to uppercase.
Step 2: Create a Shared Module
Create a SharedModule
that declares and exports the pipe:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomPipe } from './pipes/custom.pipe';
@NgModule({
declarations: [CustomPipe],
imports: [CommonModule],
exports: [CustomPipe]
})
export class SharedModule { }
Step 3: Import Shared Module into Other Modules
Now you can use CustomPipe
in multiple feature modules by importing SharedModule
:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FeatureComponent } from './feature.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [FeatureComponent],
imports: [CommonModule, SharedModule]
})
export class FeatureModule { }
Step 4: Use the Pipe in Templates
In FeatureComponent
template:
<p>{{ 'hello world' | custom }}</p>
The output will be:
HELLO WORLD
Sharing Components and Directives Across Modules
The same logic used for pipes applies to components and directives. Let’s create a reusable directive and component.
Custom Directive Example
import { Directive, ElementRef, Renderer2, HostListener } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef, private renderer: Renderer2) { }
@HostListener('mouseenter') onMouseEnter() {
this.renderer.setStyle(this.el.nativeElement, 'background-color', 'yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.renderer.setStyle(this.el.nativeElement, 'background-color', 'transparent');
}
}
Reusable Component Example
import { Component } from '@angular/core';
@Component({
selector: 'app-card',
template: `
<div class="card">
<ng-content></ng-content>
</div>
`,
styles: [`
.card { padding: 20px; border: 1px solid #ccc; border-radius: 8px; }
`]
})
export class CardComponent { }
Include in SharedModule
@NgModule({
declarations: [CustomPipe, HighlightDirective, CardComponent],
imports: [CommonModule],
exports: [CustomPipe, HighlightDirective, CardComponent]
})
export class SharedModule { }
Use in Feature Module
@Component({
selector: 'app-feature',
template: `
<app-card appHighlight>
<p>{{ 'angular modules' | custom }}</p>
</app-card>
`
})
export class FeatureComponent { }
Best Practices for Module Organization
- Feature Modules: Group related functionality together. Each feature module should be self-contained.
- Shared Module: For reusable components, directives, and pipes used across multiple modules.
- Core Module: For singleton services and app-wide singletons. Import this module only in
AppModule
. - Avoid Re-importing Shared Modules in Lazy-loaded Modules if they contain services, to prevent multiple instances.
Lazy Loading and Module Imports
Lazy-loaded modules only load when their route is activated. Importing shared modules into lazy-loaded modules works the same way. Ensure services intended to be singleton are provided in the CoreModule
or with providedIn: 'root'
to avoid multiple instances.
Common Pitfalls
- Forgotten Exports: Declaring a component or pipe in a module without exporting it will make it unavailable to other modules.
- Circular Dependencies: Avoid importing modules into each other in a circular manner.
- Duplicate Providers: Providing the same service in multiple modules can create multiple instances. Use
providedIn: 'root'
for singletons.
Leave a Reply