Angular: Understanding Modules and Services

…and using them to better organize applications!

Here you are! You are starting to feel comfortable with this framework, and all of the sudden…

Where should I put my services? Am I importing them in the right way? Are my modules well-organized and ready to scale? Is this component in the right place?
How do I even name this folder?

I found that most developers struggle with this questions because they don’t have a clear vision of how Angular Modules work and how important they are for the Dependency Injection mechanism.

In this article we try to lay the foundation for all of your next projects, nothing less! 😃

Feature Modules

In Angular, every module which is not the AppModule is technically a Feature Module, and it has the following caveats:

— It must declare all the components, directives and pipe it needs

We’ll talk about module-scoping soon, but for now it’s important to understand that is not enough to declare a component once in the declarations array of AppModule: in order to use a component in a module, it must be declared in that specific module. The same applies for directives and pipes.

— It must import CommonModule instead of BrowserModule

While BrowserModule must be imported in AppModule (it’s required in order to run the app in the browser), this module must not be imported elsewhere: instead, we must import CommonModule, which contains Angular’s common directives, such as ngIf, ngFor, ngClass, etc… BrowserModule also re-exports CommonModule, so that you can use this directives in AppModule too.

— It doesn’t bootstrap anything

The only module responsible for bootstrapping a component is, obviously, AppModule!

We’ll use Feature Modules to define all of our views, as each view (or scene) will have its own module! And since we’re talking about scenes, we’re also talking about routing. Each “View Module” (let’s call it this way) can be lazy loaded by the router, I already mentioned that in an article. 😉

Lazy loading not only saves bytes and memory, but it also enforces you to think about modules in the right way: for example, each module should have its own routing module! And here’s a little trick… if you organized your modules this way, you can still use the loadChildren keyword without actually Lazy Loading the module, as a proof that your module is independent and well-structured:

This way there’s no need for the parent module to import your child module’s components to put them in the routing configuration: from localhost/contacts/ on, the ContactsModule will be responsible for its routes. Isn’t that awesome?

Another benefit from using the Lazy approach is that we can also lazy load all the modules and preload them as soon as the app starts:

Even better, we can define our custom strategy for preloading modules! We just need to set an attribute on the routes we want to preload, and write a new class to be used as our preloadingStrategy: this class will check for the routes’ attributes and if it finds them, the router will preload those routes!

Core Module

The answer to the question “Where should I put all my global services?” would be: AppModule. This is because services are app-scoped, which means that they can be accessed from every module.

This means that if we provide a service in a child module, that service is still available in the parent module! How’s that even possible? That’s because they share the same injector! So you might think that if this is the case, you could provide the services wherever you want… Wrong!

What I just said is true, but not if those children modules are lazy loaded: every lazy module has its own injector! What that means is that a service provided in a lazy module is only accessible in that module. But it can still access the services previously provided by non-lazy modules (such as AppModule)!

Sooo… where should you put your services, such as AuthService, UserService, etc? Technically, in AppModule, since they’ll be available to everyone. However, we really don’t want our AppModule to be a complete mess… What Angular recommends is to put all of our global services in a separated module, called CoreModule, and import it ONLY in AppModule. This way is the same as providing the services in AppModule directly!

Wait… what if someone else imported CoreModule? Can we prevent it? Yes, we can! We can use a little trick to do that: inside our CoreModule, we can… inject CoreModule! If Angular injects it correctly, it means that a CoreModule has been already created, and we can throw an error:

This is particulary useful if you’re in a team with some unexperienced devs and you want to make sure nothing strange happens 😃 You can use the same approach to make sure your singleton services are singletons, just inject them into themselves and if they are injected, throw an error!

Note: We’re using the Optional decorator, because this dependency is obviously not required (it will be null in the right scenario), and we’re using SkipSelf because we’re asking for a CoreModule dependency inside CoreModule! At this point Angular would go WTF but thanks to this decorator, injection begins after CoreModule is been instantiated.

Time for the next question: where do I put my reusable components? In a Shared Module, of course!

Shared Modules

A shared module is the perfect place to declare components in order to make them reusable: this way, you won’t re-import the same components in every module, you’ll just import the shared module.

Notice the “exports” property which makes components, directives, pipes and even other modules available to the importing modules!

That’s awesome! But there’s a problem. 😅 Not with shared modules themselves, they’re fine! There’s a problem with lazy modules!

Lazy modules have a strange behavior: since they have their own injector, if they import a module which provides some services, they’ll create their own instances of those services. Gasp! Does that mean that due to lazy modules’ behavior, we cannot provide services in our shared modules?!

Well, you might think that we already declare our global services in CoreModule, and it’s true, but what if the components declared in the shared module needed some services? What if the module that imports the shared module (perhaps a lazy module?) needs a service from it? We can still work it through. 👍 👍 👍

Angular gives us a special interface we can use to attach services to modules, it’s called ModuleWithProviders, here it is:

Tiny, but useful!

What’s interesting is that we can import an object with this particular shape instead of a normal module! Our mission will be the following:

  • In our AppModule, we’re going to import the shared module with the providers attached to it
  • In all other modules, we’ll be importing the module without any provider, since they’ll be already provided in AppModule

So, how do we proceed? It’s simple: we don’t provide our services in the SharedModule metadata; instead, we define a static method inside the module, which returns the SharedModule istelf AND the array of providers!

Awesome! Now, here’s what to do: in the AppModule you can import SharedModule.forRoot(), while in all the other modules you can import SharedModule. This way the services will be provided only at AppModule level (and accessible everywhere) while the other modules still have access to the components.

Psst…

Have you already seen something like this? Yes, this is how RouterModule works! 😄 You use RouterModule.forRoot() in your AppModule and RouterModule.forChild() in all other modules. It has a second method forChild just because you need to provide an array of routes, but if you don’t need to pass anything to the shared module, it’s useless to have a second method, just import the module.
Actually, this
forRoot thing is a convention adopted by a lot of Angular libraries, but of course you could name that static method however you want.

Importing services

So, our services could be provided in 4 different places:

  • in the CoreModule, which will hold our global and singleton services
  • in a SharedModule, which will hold the services needed by itself
  • in the “View Module” itself, if the services are needed only by that module
  • in a component, if they’re only needed there

While there’s no problem for the latter 2 scenarios, importing the single services from Core/SharedModule could be messy. Imagine that one of your View Modules is located some layers deep in the folder structure; in order to import and use a global service you’d have to write something like this:

import { AuthService } from ‘../../../../core/services/auth.service’;

That’s awful! And what if the folder structure changes?

What we can do, very easily, is to add a few lines to our tsconfig.json file, this way:

Also, we’d like to import our services from ‘@app/core’, not from ‘@app/core/services/auth.service’, which is tedious. For that, in our CoreModule folder, use can create an index.ts file and re-export all of our services: now we can import directly from ‘@app/core’!

core/index.ts

Final situation

This is more-or-less how we’re supposed to structure our modules and folders:

From there, you can obviously extend your application with different shared modules (perhaps a UIKitModule, why not), or you could enrich your SharedModule with other modules. Don’t be afraid to create modules, they don’t bite!

What can we do next

We talked a lot about modules, but apart from that, there’s a lot more we can do in order to have a well-structured application. We can:

  • Write stateless micro-components instead of bloated stateful ones
  • Use ChangeDetection.OnPush on our stateless components to avoid common performance pitfalls (but carefully)
  • Use Router Guards to protect our routes
  • Use Route Resolvers to remove dependencies from our View Modules
  • Use Interceptors to handle authentication logic and other stuff
  • Use libraries like Redux or MobX to handle the business logic of the entire application

… and so on! But there’ll be time for other articles 😉 For any question or request, please don’t be shy and leave a comment!