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 .
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:
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?
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 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:
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).
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.
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; }
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:
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 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.
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';
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.
Example of a global module for service
@NgModule({ providers: [SomeService] }) export class SomeModule {}
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.
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.
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.
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 {}
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.
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';
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.
UI module example
@NgModule({ imports: [CommonModule], declarations: [PublicComponent, PrivateComponent], exports: [PublicComponent] }) export class UiModule {}
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.
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