Learn how NgModules works
A lot of engineering work was done by the Google team to develop Angular’s own NgModule system. This system was quite criticized at the beginning of the framework's existence because it is true that it is quite complex. Indeed many beginners in Angular have been repelled by the complexity of these Angular modules. This is why Angular later chose to simplify it slightly via the default providedIn 'root' services and thus avoid more or less serious service lifecycle configuration errors. Finally with the appearance of Standalone Components in version 14 of Angular, NgModules have become optional and thus the majority of new projects will be developed without NgModules however all projects started before version 14 will surely keep traces of the past even if the Angular team provides automated migration methods. If you are working on a project with NgModules do not be afraid, the module system is very well designed and with a minimum of learning which we will see together it becomes pleasant to use them.
Types of modules
- App module: it's the root module used to bootstrap the application. There should not be a lot of content because most of the codebase must be encapsulated in the Core module and then into domain-specific modules called features modules.
- Core module: app-level components like header, footer, breadcrumb, navigation and singleton services. This module should be imported once in app module and the module should not export anything because only the core use those components.
- Shared module: shared components, directives and pipes used throughout the application must be inside this module and exported to be able to be used by others modules which are importing it. It's common to import and export Angular built modules, for instance Common module or Material module inside your Shared Module if you really need to access them in multiple locations like in many Feature modules. In order to optimize your app at best this module should not be imported in App or Core modules, like that it will be lazy loaded once the user will start to use the first lazy loaded feature module. See below one shared module vs multiple shared modules. And because your Shared module(s) will be imported by many lazy loaded features they must only contain declarables and modules which only contain declarables and must never implement any services via the providers array else you will have issue with the singletons.
- Feature module: Dividing things into domain-specific, lazy-loaded modules will help your project in the long run. These modules must be isolated and must not export anything other than the module itself to perform lazyloading.
Developing an Angular application by understanding the concept of lazy modules is important, indeed the advantages of using this pattern bring many advantages.
- Faster application boot time, indeed if you split your app into multiple lazy loaded feature modules the main bundle will stay small and thus your app will be fast to load and boot.
- Faster rebuilds in dev mode, indeed during development it is also faster since the compiler has only to rebuild the lazy modules affected by your changes and not the whole application.
- Feature isolation help your app maintenability and also it guarantees a better scalability since your code is easier do understand and work on.
One shared module
- If we put all shared pipes, directives and common components in one big shared module and then import it everywhere (inside sync and async chunks) then this code will be found after compilation in our initial main chunk. So this is not the recommended method especially if your Shared module contains a lot of things.
Multiple shared modules
- On the other hand, if we split commonly used code across lazy loaded modules then a new shared chunk will be created and will be loaded only if any of those lazy modules are loaded. This should improve the application initial load. But do it wisely because sometimes it’s better to put small directives in one chunk that having the extra request needed for a separate tiny chunk load.
The declarations array only takes declarables. Declarables are components, directives and pipes. Declarables must belong to exactly one module else the compiler emits an error. Declarables are private by default so they can be used only in the template of any component that is part of this same NgModule, if you want to use it outside you must uses the export array. Pipes are a special case, they must also be added to the providers array if you use transform function in a component template or if you want to use inject it via a constructor. You can also instead provide your pipe locally to the component or else use the providedIn 'root' if you want to share it globally as singleton.
Other modules whose exported declarables are needed by component templates declared in this NgModule. Basically this is useful in order to use declarables that are exported in others modules. For instance if ModuleA imports ModuleB then ModuleA will be allowed to use any declarable exported by ModuleB.
The subset of declarations that should be visible and usable in the component templates of other NgModules. It means that a template external to this module can use exported declarables from any imported module. For instance if you want to use in your app template a modal component declared in a modal module then this modal component should be declared and exported in your modal module first and of course modal module should be imported in your app module. Also another more complex case is the indirect export principles made possible by exporting a whole module instead of a single declarable. Indeed if ModuleA imports ModuleB, and also exports it, this makes the declarables from ModuleB available wherever ModuleA is imported.
The providers array is part of the dependency injection, you will learn more about this part in this dependency injection course.