📜 ⬆️ ⬇️

Angular application architecture. We use NgModules

Note Translation: To understand this article, you must have basic knowledge of Angular: what are components, how to create a simple SPA application, etc. If you are not familiar with this topic, then I recommend first to get acquainted with the example of creating a SPA application from the office. documentation.


About NgModules can be read here .


image


One year ago, I published an article about NgModules , which deals with technical details when to import modules, namespace, etc. Recommended for familiarization (comment perev .: article on the content is similar to the one to which I refer at the beginning) .


I recently accepted the challenge that Angular threw me. So far, I have used the approach suggested by the official Angular documentation. But reaching the big project began to show weaknesses.


I began to study in detail the manual on NgModules , which has grown to as many as 12 pages of detailed descriptions with the FAQ. But after a careful reading of the questions, more than answers arose. For example, where is it better to implement the service? There was no clear answer to this question. Moreover, some solutions contradict each other in the context of the manual.


After digesting the entire section about NgModules I decided to implement my decision on the architecture of Angular applications, based on the following:



Angular modules


What are Angular modules?


In fact, the main purpose of the module is the grouping of components and / or services related to each other. And, in general, nothing more. For example, imagine the news block on the main page. If roughly, then the visual part is the component, and the mechanism for retrieving data from the database is the service.


For those familiar with Java, the Angular modules are packages, and in C # / PHP, the namespace.


Only one question remains - how to properly group application functionality?


Types of Angular Modules


There are only 3 of them:



Once you have created the startup application via ng new projectname
then at least you have created a page module. In this case, one - the main one.


As your application grows, you will create new modules for pages, services, components, and group them together. If, of course, you want to get a serviceable and scalable application, and not to merge all the functionality in one file.


Page Modules


Page modules have routing and are designed to logically separate areas of your application. Page modules are loaded once in the main module (usually called AppModule ) or via lazy load.


For example, on the login, exit and registration page, you need the AccountLogin module; HeroesModule for the hero list page, hero page, etc. (comment perev .: here is meant educational project , which is described in the official documentation) .


Page modules can contain:



Public Services for Pages


To display data on the page, you first need to take this data from somewhere. For this and need services


 @Injectable() export class SomeService { constructor(protected http: HttpClient) {} getData() { return this.http.get<SomeData>('/path/to/api'); } } 

Subsequently, some pages will need similar data, which means services of the same type. In this case, it is necessary to make one service and publicly available in the entire application, and not in a specific module.


But for best practice, it is better to design the module so that a particular page requires a certain type of data, a specific service. In this case, you need to encapsulate this service and restrict access to it within a single module, and not the entire application.


Note transfer.


With this architecture, your application will be easier to maintain, because All application logic will be divided into blocks, responsible for the implementation of a specific functionality. If everything is merged into one service and made available throughout the application, then there will be problems with expanding the functionality, leading to a contradiction between the principles of interface separation, common responsibility and other SOLID. However, how to design the architecture of your application is up to you.


Let's go back to the AccountManager module, which was voiced earlier as an example. The service of this module, AccountService , should be "thin" and respond, if necessary, with "yes" or "no", depending on the role model of the user. User status (online or not) cannot be implemented in this service, since This module may not be necessary in some parts of the application. Therefore, the user status must be transferred to the global service, which will be available throughout the application (see below).


Modules-pages: routing


The page component is responsible for presenting information from the database, which is retrieved by the service.


You can display data directly in the component, but you are not required to do so. You can pass the data as a variable to another component.


 @Component({ template: `<app-presentation *ngIf="data" [data]="data"></app-presentation>` }) export class PageComponent { data: SomeData; constructor(protected someService: SomeService) {} ngOnInit() { this.someService.getData().subscribe((data) => { this.data = data; }); } } 

Each component has its own route.


Components for data visualization


The data presentation components extract information using the @Input decorator and display in their template


 @Component({ selector: 'app-presentation', template: `<h1>{{data.title}}</h1>` }) export class PresentationComponent { @Input() data: SomeData; } 

Is it MVx?


Anyone familiar with the model-controller-view-pattern will wonder if this is it? If you follow the theory, then no. However, if it is easier for you to imagine the architecture of Angular using MVx, then:


services comparable to Models ,
presentation components are similar to View ,
page components will be Controllers \ Presenters \ ViewModels (choose what you use).


Despite the fact that this is not quite MVx (or not at all MVx), the goals in this approach are the same - sharing responsibility in solving problems. Why is it important? That's why:



Summing up


Sample page module


 @NgModule({ imports: [CommonModule, MatCardModule, PagesRoutingModule], declarations: [PageComponent, PresentationComponent], providers: [SomeService] }) export class PagesModule {} 

where the service is encapsulated in this module.


Global Services Modules


Global services modules provide access to your service anywhere in your application. Since such services have a global scope, these modules are loaded only once into the root module ( AppModule ) and are available everywhere, including when implementing lazy load.


You have definitely used at least one such service. For example: HttpModule . But soon you will need your service, similar to HttpModule . For example - AuthModule , which stores the current status of the user and his token, and is needed throughout the entire application, the entire user session.


Usability


If you are careful in designing a module for a global service, make it without a visual part, break the service logic into separate modules and design at the interface level, rather than implementing a specific application (that is, you will not embed dependencies of a specific application), then modules can be used in other projects.


It should be noted that if you want to make the module available in other projects (i.e., from outside), you need to create an entry point for it, where you export the NgModule, the interface, and possibly the tokens for the implementation.


 export { SomeService } from './some.service'; export { SomeModule } from './some.module'; 

Should I do the coremodule


Not necessary. Official documentation suggests to implement all global services in CoreModule. You can certainly group them into / core / modules , but pay attention to the division of responsibility and do not "merge" everything into one CoreModule . Otherwise, you can not use the implemented functionality in other projects.


Total


Example of a global module for service


 @NgModule({ providers: [SomeService] }) export class SomeModule {} 

UI components and how to get data


UI components (for example, widgets) are “thin” and are responsible only for the visualization of the received data, as was discussed above in the “page modules”. A component receives data using the @Input decorator (sometimes from <ng-content>, and sometimes other solutions).


 Component({ selector: 'ui-carousel' }) export class CarouselComponent { @Input() delay = 5000; } 

You do not have to rely entirely on the service. Why? Because services have their own specifics depending on the offer. For example, the URL of the API may change. Data presentation is a matter of components inside module pages. UI components receive data provided by someone, but not by them.


Open (public) and hidden (private) components


In order to make a component available (public) you need to export it in a module. However, you do not need to import everything. Nested components should \ can remain hidden (private) if they are not needed elsewhere in the application.


Directives and pipelines


If we talk about modules for directives and pipes, then it is similar with UI components. If necessary, we export in a module and use where we like.


Hidden (private) services


To work with data exclusively inside the component UI, you can implement the service only inside the component, not the NgModule and make it closed for everything except its component. In this case, it will look like this.


 @Component({ selector: 'some-ui', providers: [LocalService] }) export class SomeUiComponent {} 

Public (public) services


Imagine a situation where you want to open access to the service that is implemented in the UI component. This should be avoided as much as possible, but it can be implemented.


We open access to the service in NgModule and get the problem of multiple module loading, and with it the service, since in the module we implement the component.


To solve this problem, you need to implement the module in this way.


 xport function SOME_SERVICE_FACTORY(parentService: SomeService) { return parentService || new SomeService(); } @NgModule({ providers: [{ provide: SomeService, deps: [[new Optional(), new SkipSelf(), SomeService]], useFactory: SOME_SERVICE_FACTORY }] }) export class UiModule {} 

By the way, this is how it was implemented (at least it was) in the Angular CDK.


Usability


To use UI components as modules, you need to export components \ pipe \ directives, etc., and open access to them by creating an access point


 export { SomeUiComponent } from './some-ui/some-ui.component'; export { UiModule } from './ui.module'; 

Do I need to do a SharedModule?


Whether to merge all the entire user interface (UI components) in the SharedModule Definitely not. Although the documentation suggests this solution, each module implemented in the SharedModule will be implemented at the project level, not on the interface.


There are no problems in importing dependencies when creating a project, especially by automating this process in VS Code (or other IDEs).


However, where the best tone would be to create separate modules for each user interface entity and put them in the / ui folder, for example.


Total


UI module example


 @NgModule({ imports: [CommonModule], declarations: [PublicComponent, PrivateComponent], exports: [PublicComponent] }) export class UiModule {} 

What is the result?


If you will design your application taking into account the above, then:
You will have a well-structured architecture, whether in small or large applications, with or without lazy load.
You can pack global modules or UI components into libraries and use them in other projects.
You will test applications without agony.


Project structure example


 app/ |- app.module.ts |- app-routing.module.ts |- core/ |- auth/ |- auth.module.ts |- auth.service.ts |- index.ts |- othermoduleofglobalservice/ |- ui/ |- carousel/ |- carousel.module.ts |- index.ts |- carousel/ |- carousel.component.ts |- carousel.component.css |- othermoduleofreusablecomponents/ |- heroes/ |- heroes.module.ts |- heroes-routing.module.ts |- shared/ |- heroes.service.ts |- hero.ts |- pages/ |- heroes/ |- heroes.component.ts |- heroes.component.css |- hero/ |- hero.component.ts |- hero.component.css |- components/ |- heroes-list/ |- heroes-list.component.ts |- heroes-list.component.css |- hero-details/ |- hero-details.component.ts |- hero-details.component.css |- othermoduleofpages/ 

If you have comments on this architecture, then please leave your comments.


- Telegram of the Russian-speaking Angular Community.


')

Source: https://habr.com/ru/post/351678/


All Articles