Why and how to structure Features in Modules in Angular

Philipp Bauknecht
medialesson
Published in
4 min readJun 28, 2018

--

This might sound pretty basic, but I encounter these challenges over and over in customer projects and it’s still an ongoing discussion internally.

A central project goal in a recent Angular project was to design features and UI components for reusability. To achieve this, we need to make sure our code is well isolated and has a simple and clear dependency model.

Prologue: Feature vs. Technical Project Structure

When building small apps and looking at common code samples in the internet a lot of devs (including myself) tend to come up with a project structure like this:

The code is structured by technical aspects like ViewModels, Models, Views, Services etc. And a lot of times we find folders like Helpers which usually host everything that didn’t fit into one of the standard folders.

When building a larger app, it’s generally a better idea to structure code by features a style that’s also recommended by Angular which might look like this:

This way it’s much easier to find stuff because the structure reflects what the user sees in the app. The code becomes also more resilient to changes as features are isolated from each other and features or UI components can more easily be reused and tested. Also in the case of Angular the code style guidelines suggest putting the type of class we’re creating into the class name like [my-name].component.ts or [my-name].service.ts making it useless to additionally group all services into a service folder as it’s already visible in the filename that it’s a service.

Why put a Feature into a Module?

Just grouping services and components into folders by feature doesn’t fully deliver on the above idea. Using a module to define a feature in Angular allows for isolation, portability and lazy loading. So we choose to group features in modules by default (no rule without exceptions…).

Example: Blocking Progress Indicator

A good sample for the mentioned benefits is a blocking progress indicator that overlays the current UI with a progress animation whenever a feature needs to block the UI from interaction e.g. to do an asynchronous operation.

Spinner Module

First, we need to create a module and a component with a simple loading animation. So let’s take one of the awesome pure CSS3 animations from Tobias Ahlin’s SpinKit project and wrap it in a component and in a module.

Blocking Progress Module

Now to our BlockingProgressModule. This module will import the SpinnerModule and add additional blocking UI as well as an API to enable/disable the animation.

To allow external components or services to toggle the animation we can use a service with observables. Our UI component then binds to those observables to show/hide the animation and external code can trigger those observables.

So, let’s add a small component to render the SpinnerComponent and bind to the service.

Use Module in multiple modules

The BlockingProgressModule is made to be used across multiple components and it’s UI will be hosted in the main app module. We could just import the BlockingProgressModule into our AppModule and then go and inject the service into whatever child module we like. But this would mean that we introduce nontransparent dependencies into those child modules as they rely on a service that’s provided by a module that’s not directly imported into the child module by rather been passed on through the dependency tree. Of course this is not a real problem if you’re not planning to reuse the child module anyway.

So let’s import the BlockingProgressModule into every module that uses it. Now we have a new challenge: As we import the module multiple times (AppModule + child modules) multiple instances of our service are being created at runtime making the service pretty much useless.

To solve this issue we can simply use the .forRoot() method as described here to create a singleton instance of our module’s services. To make this work we need to provide our BlockingProgressService different using the static forRoot() method:

Then import the module in our AppModule using the forRoot() method:

All child/feature modules will import the BlockingProgressModule the standard way like so:

Shared/Core

Our BlockingProgressModule is a good example of a feature that will be shared across multiple feature modules. Another one would be authentication or custom directives. While one could argue that these are features of their own, it might still be handy to group them for usage convenience and a better overview it the app’s root folder into a core module. A good entry into the discussion around shared and core modules is provided here: https://angular.io/guide/ngmodule-faq#what-kinds-of-modules-should-i-have-and-how-should-i-use-them

Conclusion

Hope this explanation provides some context to those who struggle to structure and connect modules in Angular. There might not be a one fits all approach and we’re still discussing aspects of this internally. Please feel free to provide feedback.

--

--

Philipp Bauknecht
medialesson

CEO @ medialesson. Microsoft Regional Director & MVP Windows Development. Father of identical twins. Passionate about great User Interfaces, NYC & Steaks