Contact

A Better Way to Share Components

Jaroslaw Zolnowski
January 20, 2023

Introduction

In order for large web applications to be scalable, they are usually broken up into a number of modules. Very often, we need to reuse individual Components, Pipes, and Directives in many places of our application. Duplicating code for repetitious items is not the best practice. This is why we usually create a shared module, registering all reusable and generic elements of our application there, so that we can use them later in any feature module.

It makes sense to create a global place for all reusable items. Unfortunately, it also has disadvantages.

As the project evolves and the demand for more and more reusable elements increases, the shared module can grow significantly. We must be aware. that in order to use one component, we have to import all the elements registered in the shared module, which becomes inefficient.

Shared Modules

Shared Modules Image

Let's look at the situation from a sample application.

Angular Icon

We have defined 2 modules, AppModule and UsersModule. They both use ButtonComponent. Additionally, UsersModule uses a FilteredListComponent with users, which is also a reusable component - it accepts any shape of a single list element. We put both reusable components in a shared module.

export module SharedModule {
  export const name: string = 'Shared';

  angular
    .module(SharedModule.name, […])
    .component(FilteredListComponent.componentName, FilteredListComponent.componentConfig)
    .component(ButtonComponent.componentName, ButtonComponent.componentConfig);
}
angular.module('AppModule', [SharedModule.name])
angular.module(UsersModule.name, [SharedModule.name]);

As we can see, AppModule needs to import the entire SharedModule to access the reusable ButtonComponent.

Now imagine that our application defines hundreds of such reusable elements. We won't need all of these elements in every module. A Single Component Angular Module ("SCAM") comes to the rescue.

What is SCAM?

Single Component Angular Module, or SCAM for short, is the concept of creating small, encapsulated and reusable AngularJS / Angular modules with only one Component (or Directive, or Filter / Pipe).

SCAM in action

Instead of a global module with elements shared between the entire application, we can define modules for each of the elements:

export module ButtonModule {
  export const name: string = 'ButtonModule';
  angular
    .module(ButtonModule.name, ['ngMaterial'])
    .component(ButtonComponent.componentName, ButtonComponent.componentConfig);
}
export module FilteredListModule {
  export const name: string = 'FilteredListModule';
  angular
    .module(FilteredListModule.name, ['ngMaterial'])
    .component(FilteredListComponent.componentName, FilteredListComponent.componentConfig);
}

We can see that we can easily build a module architecture that provides only the required elements for given views, without the need to import unused Components, Filters / Pipes, or Directives.

export module UsersModule {
  angular.module(UsersModule.name, [ButtonModule.name, FilteredListModule.name]);
}
module AppModule {
  angular.module('AppModule', [ButtonModule.name]);
}

SCAM and Refactoring

Note that the SCAM-based architecture allows you to refactor the code quickly. If you reach the point where UsersModule no longer requires the use of buttons, you would need to delete the dependency to remove it. Adopting this method gives you a considerable advantage over shared modules when migrating from AngularJS to Angular. You can now migrate application sections more efficiently by breaking them into independent but valuable parts because you depend on only one component and not on sets of components.

SCAM and Standalone Components

Another point I would like to make is the similarity with Standalone Components. Let's recall that since Angular version 14, we can write code without using Angular modules. Having SCAM defined:

import {UpperCasePipe} from '@angular/common';

@Component({
  selector: 'app-button',
  template: `
    <button mat-button>Sample Button</button>
  `,
})
export class ButtonComponent {
  // your component's code
}

@NgModule({
  declarations: [ButtonComponent],
})
export class ButtonComponentModule {
}

we can easily migrate to Standalone Components without worrying that we messed up something in other parts of the application, since it is an independent piece of code.

@Component({
  standalone: true,
  selector: 'app-button',
  template: `
    <button mat-button>Sample Button</button>
  `,
})
export class ButtonComponent {
  // your component's code
}

Conclusion

Using common SCAMs creates some overhead because you have more modules and more imports in your feature modules. On the side. On the other hand, you get smaller, better encapsulated modules that are less cumbersome to maintain. SCAM-based code refactoring is much more straightforward and less time-consuming. Even more complicated migrations from AngularJS to Angular will be simpler to implement, and applying Standalone Components in the project will reduce the risk of bugs.

As I mentioned SCAM is a concept, not imposed by a specific framework or API version. So it can be used wherever we are dealing with shared components.

The source code for this example is in this GitHub repository.

Looking for more support for your Angular projects?

Our team at DevIntent is highly experienced in web development, including framework migration and updates. Check out our offerings or reach out.

Jaroslaw Zolnowski
January 20, 2023