Typical enterprise applications usually consist of a backend and a frontend system. The backend is responsible for interacting with a database for data persistence and exposes a REST API. The frontend communicates with the backend system via the REST interface to exchange data. In some cases, the frontend system can consist of more than one application, including a web interface or a mobile application. Keeping all these applications and systems in separate source control repositories does not scale well, and it is not easy to maintain and build. Alternatively, we can follow monorepo architecture for such large enterprise applications, where each application resides in a separate location inside the same repository.
A popular tool in the Angular ecosystem that embraces monorepo architecture is Nx Dev Tools. Combining an Nx monorepo application with a state management library can significantly level up your application. NgRx, a popular state management library for Angular applications, can help us keep a consistent and manageable state globally.
In this chapter, we will investigate both technologies by building an enterprise portal application for visiting points of interest (POIs). We will cover the following topics:
Nx Dev Tools is a suite of development tools and libraries for building web applications based on monorepo architecture. A typical Nx application can contain many applications and shared libraries inside a single workspace. The flexibility of monorepo architecture allows for any type of application, backend or frontend, to use the same libraries inside the workspace.
Important note
In this project, we will consider only frontend applications built with the Angular framework.
Nx Dev Tools provides developers with the following features:
In a large Angular enterprise application, maintaining a consistent global state is a tedious process. The use of @Input and @Output decorators to communicate between Angular components is not always viable, especially when there are many levels of components that need to share the same state.
NgRx is a library that provides efficient management of the global application state powered by the RxJS library. The main building blocks of NgRx are the following:
In this project, we will build an Angular enterprise application for managing POI visits on a map. The application will consist of two portals where one will allow a visitor to select a POI from a list and view its location on a map. Another portal will allow an administrator to view the traffic of each POI.
First, we will build an Angular application from scratch using Nx Dev Tools. We will then create the skeleton of each portal by adding the essential components of our application. After we have scaffolded our application, we will start adding the functionality of the visitor portal using NgRx. Finally, we will implement the administrator portal and learn how to use an Angular library for visualizing data in a graph.
Build time: 3 hours
The following software tools are required for completing this project:
Nx Dev Tools provides developers with tools for working with monorepos, including the following:
Tip
It is recommended to use the Quick Open feature of VSCode when working with Nx monorepos. The number of generated folders and files will significantly increase, and it will be challenging to navigate through them. You can find out more at https://code.visualstudio.com/docs/editor/editingevolved#_quick-file-navigation.
When we use the preceding npm package to create a new Nx monorepo workspace, it will check whether Nx CLI has already been installed. To install Nx CLI, run the following command in a terminal:
npm install -g nx
The preceding command will install the nx npm package globally on our system. We can now scaffold a new Nx monorepo workspace using the following command:
npx create-nx-workspace packt --appName=tour --preset=angular --style=css --linter=eslint --nx-cloud=false --routing
The preceding command uses npx, an npm command-line tool that allows us to execute an npm package without installing it first. It executes the create-nx-workspace package passing the following options:
Important note
Creating a new Nx workspace may take some time to complete, as it installs all the necessary packages for an enterprise environment.
After the creation of the workspace has been completed, we can run it to verify that everything has been set up correctly:
Congratulations! Your new application has been configured correctly! Nx creates a minimal skeleton application just like Angular CLI does for our convenience so that we can build our features on top of that.
In the next section, we will dive deeper into Nx Dev Tools by creating the administrator and visitor portals in our workspace.
Our application will consist of two portals that different users will use. Visitors will be able to view a list of POIs and select them on a map. Administrators will be able to view statistics for each POI. We will learn more about how to use Nx Dev Tools in the following sections:
Each portal will be a separate Nx library that will be loaded according to the URL entered in the address bar of the browser. Organizing our code in libraries allows us to reuse it between different applications and build and test it individually. We will start building the visitor portal in the following section.
The visitor portal will be a library inside the Nx workspace that will be loaded by default. Let's see how we can build that library with Nx Console:
Important note
If you do not see the option in the dialog, restart VSCode.
Tip
When you were typing the name of the library, you may have noticed that Nx was running the generate command in the terminal. Well, it did not run it actually. Instead, it was mimicking the effect of running the command in your system; a technique called dry run.
Nx will create the visitor library inside the libs folder of our workspace. The library does not contain any components yet. According to the project specifications, the visitor portal will contain a list of POIs where the user will be able to select them and view their location on a map. Thus, we need to create an Angular component with the following layout:
In the previous diagram, the portal consists of the sidebar that displays a POI list and the main content area for displaying the map. Instead of creating the layout from scratch, we will use Angular Material, which contains a handful of ready-made layouts, including one with a sidebar.
Before working with Angular Material, we need to install it in our application with the following command:
ng add @angular/material --theme=deeppurple-amber --typography=true --animations=true
We use the ng add command of the Angular CLI to install the @angular/material npm package, passing additional options. You can learn more about the installation of Angular Material and its available options in Chapter 4, Building a PWA Weather Application Using Angular Service Worker.
Installing Angular Material in our application will also install the @angular/cdk npm package. @angular/cdk is called the Component Dev Kit (CDK) and contains specific behaviors and interactions used to build Angular Material.
Important note
The CDK can be used to build custom UI libraries without the need to rely on Angular Material. We will learn how to build such libraries in Chapter 9, Building a Component UI Library Using Angular CLI and Angular CDK.
The Angular Material library contains the following component templates that we can use:
In our case, we will use the navigation component because we need a sidebar. Let's see how we can generate that component:
Important note
If the library does not appear in the dropdown, restart VSCode.
The component will be the main one of the library, so we want to have it in the same folder with its related module file.
Tip
There is no need to define the module where the component will be created because the Angular CLI can deduce it directly from the path option.
Nx Console will create the visitor component in the visitor library of the Nx workspace. We now need to connect it with the main application of the workspace:
<router-outlet></router-outlet>
The router-outlet component is part of the Angular router and is a placeholder where we will load our portals.
import { RouterModule } from '@angular/router';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, BrowserAnimationsModule,
RouterModule.forRoot([
{ path: 'tour', loadChildren: () =>
import('@packt/visitor').then(m =>
m.VisitorModule) },
{ path: '', pathMatch: 'full', redirectTo:
'tour' }
])
],
providers: [],
bootstrap: [AppComponent],
})
The route configuration contains two paths. The default path, denoted by the empty string, redirects to the tour path. The tour path lazily loads the module of the visitor library.
The @packt prefix in the import method is the organization name that we configured when creating the Nx workspace. When we want to import an artifact of our project, we will import it directly from the @packt namespace. It behaves as a path alias in our project.
import { RouterModule } from '@angular/router';
@NgModule({
imports: [CommonModule, LayoutModule,
MatToolbarModule, MatButtonModule,
MatSidenavModule, MatIconModule, MatListModule,
RouterModule.forChild([
{ path: '', component: VisitorComponent }
])
],
declarations: [VisitorComponent],
})
The route configuration will activate VisitorComponent by default as soon as VisitorModule is loaded using the tour path in step 3.
If we now run the Serve command from Nx Console and navigate to http://localhost:4200, we should see the following output:
The Angular router will redirect us to http://localhost:4200/tour, and it will display the visitor portal. It currently contains some demo data that Angular Material entered when we generated the navigation component. We will revisit it in the Managing application state with NgRx section to implement the full functionality using NgRx. For the time being, we will continue by building the administrator portal in the next section.
The administrator portal will be an Nx library with a single component, just like the visitor portal, except that it will not be based on an Angular Material template. Let's start scaffolding the structure of the library using Nx Console:
Important note
If the library does not appear in the dropdown, restart VSCode.
The Angular CLI will create the admin component inside the folder of the admin library. We now need to wire up the admin library to the monorepo application:
RouterModule.forRoot([
{ path: 'admin', loadChildren: () =>
import('@packt/admin').then(m => m.AdminModule) },
{ path: 'tour', loadChildren: () =>
import('@packt/visitor').then(m => m.VisitorModule)
},
{ path: '', pathMatch: 'full', redirectTo: 'tour' }
])
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminComponent } from './admin.component';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{ path: '', component: AdminComponent }
])
],
declarations: [
AdminComponent
],
})
export class AdminModule {}
The page will display the default template of the main component of the administrator library.
We have now completed the scaffolding of our enterprise application. First, we created the Nx monorepo workspace that will host the portals of the application. Then, we used Nx Console to generate our portals along with their main components. We also installed Angular Material to use its UI elements in our components.
In the next section, we will implement the functionality of the visitor portal using NgRx.
The visitor portal will allow the user to see a list of available POIs and select one to view its location on a map. The list of available POIs and the selection of a POI is the global state of our application. We will integrate NgRx for managing the application state in the visitor portal by completing the following tasks:
Let's begin by configuring the state of our application in the following section.
Our application will consist of a root state for the whole application and a feature state for the visitor portal. We will start by executing the following command to create the root state:
nx generate ngrx app --root --no-interactive --project=tour --module=apps/tour/src/app/app.module.ts
The preceding command uses the generate command of Nx CLI, passing the following options:
The previous command will add all necessary NgRx npm packages in the package.json file and install them. It will also modify the app.module.ts file to configure all NgRx-related artifacts such as the store, effects, and Store DevTools.
Tip
Store DevTools is an npm package of the NgRx library that provides debugging, monitoring, and instrumentation capabilities to the state of an NgRx application. It must be used in conjunction with the Redux DevTools browser extension, which can be found at https://github.com/zalmoxisus/redux-devtools-extension.
The visitor library itself will not manage the data for the state of the visitor portal. Instead, we will create a new library in our Nx workspace that will fetch and store data in the feature state. Execute the following command of Nx CLI to create a new library:
nx generate lib poi
The preceding command will generate the poi library in our Nx monorepo.
Tip
The generate command of Nx CLI that we used has the same effect as creating the library using Nx Console.
Now that we have created the library, we can set up the feature state using the following command:
nx generate ngrx poi --project=poi --no-interactive --module=libs/poi/src/lib/poi.module.ts --barrels
The preceding command uses the generate command of Nx CLI to register a feature state passing additional options:
The preceding command will create a folder, named +state by convention, inside our library that contains the following files:
Nx CLI has done most of the job by adding the necessary content in the previous files, eliminating the boilerplate code for us. We now need to create an Angular service in the library that will fetch the POI data:
export interface PoiEntity {
id: string | number; // Primary ID
name: string;
lat: number;
lng: number;
description: string;
imgUrl: string;
}
nx generate service poi --project=poi
The preceding command will create an Angular service called poi in the poi library.
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { PoiEntity } from '..';
export class PoiService {
constructor(private http: HttpClient) {}
getAll(): Observable<PoiEntity[]> {
return
this.http.get<PoiEntity[]>('assets/poi.json');
}
}
We use the built-in HTTP client of the Angular framework to get POI data by initiating a GET HTTP request.
Important note
You can get the poi.json file from the GitHub repository of the Getting started section and copy it to the apps oursrcassets folder of your workspace.
import { map } from 'rxjs/operators';
import { PoiService } from '../poi.service';
constructor(private actions$: Actions, private poiService: PoiService) {}
init$ = createEffect(() =>
this.actions$.pipe(
ofType(PoiActions.init),
fetch({
run: (action) => {
return this.poiService.getAll().pipe(
map(pois => PoiActions.loadPoiSuccess({ poi:
pois }))
)
},
onError: (action, error) => {
console.error('Error', error);
return PoiActions.loadPoiFailure({ error });
},
})
)
);
The effect is responsible for listening to all actions that are dispatched in the store. As soon as a PoiActions.init action is dispatched, the init$ property is triggered and calls the getAll method of the poiService variable. The init$ property knows which action to listen for by the parameters in the ofType operator.
Tip
The ofType operator can accept more than one action.
If the data is fetched successfully, the effect will dispatch a new action in the store, PoiActions.loadPoiSuccess, with POI data as the payload. If there is a failure getting the data, it will dispatch a PoiActions.loadPoiFailure action in the store.
import { HttpClientModule } from
'@angular/common/http';
Add the HttpClientModule class also in the imports array of the @NgModule decorator.
The global state of our application is now configured and ready to be used. In the following section, we will create additional Angular components in the visitor library that will interact with the feature state of our application.
The visitor portal will interact with the feature state of our application through two Angular components. One component will display the list of POIs and allow the user to select one. The other component will display a Google Maps map and display the selected POI on it.
Initially, we will build the component that displays the list of POIs:
import { PoiModule } from '@packt/poi';
@NgModule({
imports: [CommonModule, LayoutModule,
MatToolbarModule, MatButtonModule,
MatSidenavModule, MatIconModule, MatListModule,
RouterModule.forChild([
{ path: '', component: VisitorComponent }
]),
PoiModule
],
declarations: [VisitorComponent],
})
We import PoiModule so that the poi feature state is registered in the store as soon as the visitor portal is loaded.
nx generate component poi-list --project=visitor
import { Store } from '@ngrx/store';
import { PoiActions, PoiSelectors } from '@packt/poi';
constructor(private store: Store) { }
ngOnInit(): void {
this.store.dispatch(PoiActions.init());
}
We execute the action as a method and pass its result to the dispatch method of the store variable.
pois$ = this.store.select(PoiSelectors.getAllPoi);
We use the select method of the store variable to execute the selector.
Important note
We did not create the PoiSelectors.getAllPoi selector. NgRx did it for us when we generated the feature state in the poi library.
<mat-action-list *ngFor="let poi of pois$ | async">
<button mat-list-item>{{poi.name}}</button>
</mat-action-list>
We use the mat-action-list component of the Angular Material library to display each POI as a single action item. We subscribe to the pois$ property using the async pipe and create a button element with the mat-list-item directive for each POI.
<mat-sidenav #drawer class="sidenav" fixedInViewport
[attr.role]="(isHandset$ | async) ? 'dialog' :
'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="(isHandset$ | async) === false">
<mat-toolbar>Menu</mat-toolbar>
<packt-poi-list></packt-poi-list>
</mat-sidenav>
Use Nx Console to start the application, and you should see the following output in the menu sidebar:
We have already created the Angular component that will display the list of available POIs. Let's see now how to create the component to display a POI on the map using Google Maps.
The Angular Material library contains a component for Google Maps that we can use in our application:
npm install @angular/google-maps
import { GoogleMapsModule } from '@angular/google-
maps';
@NgModule({
imports: [CommonModule, LayoutModule,
MatToolbarModule, MatButtonModule,
MatSidenavModule, MatIconModule, MatListModule,
RouterModule.forChild([
{ path: '', component: VisitorComponent }
]),
PoiModule,
GoogleMapsModule
],
declarations: [VisitorComponent, PoiListComponent],
})
<script src="https://maps.googleapis.com/maps/api/js">
</script>
Now that we have installed and registered Google Maps in our application, let's create the Angular component that will host it:
nx generate component map --project=visitor
import { Store } from '@ngrx/store';
import { PoiSelectors } from '@packt/poi';
export class MapComponent implements OnInit {
poi$ = this.store.select(PoiSelectors.getSelected);
constructor(private store: Store) { }
ngOnInit(): void {
}
}
<google-map height="100%" width="auto" *ngIf="poi$ |
async as poi" [center]="poi">
<map-marker [position]="poi"></map-marker>
</google-map>
In the preceding template, we subscribe to the poi$ property using the async pipe. As soon as we get a selected POI from the store, we display a google-map component and set the center of the map to the POI coordinates. Furthermore, we add a marker on the map in the specified POI coordinates.
<mat-sidenav-content>
<mat-toolbar color="primary">
<button
type="button"
aria-label="Toggle sidenav"
mat-icon-button
(click)="drawer.toggle()"
*ngIf="isHandset$ | async">
<mat-icon aria-label="Side nav toggle icon">
menu</mat-icon>
</button>
<span>visitor</span>
</mat-toolbar>
<packt-map></packt-map>
</mat-sidenav-content>
The Angular component that we created will show the location of a POI on the map as soon as we select it from the list. If you try to select a POI from the list, you will notice that nothing happens. Why is that?
The global state of the application does not currently know when a POI has been selected. We need to add the necessary code for setting the selected POI and interacting with the store:
export const selectPoi = createAction(
'[Poi/API] Select Poi',
props<{ poiId: string | number }>()
);
const poiReducer = createReducer(
initialState,
on(PoiActions.init, (state) => ({ ...state, loaded:
false, error: null })),
on(PoiActions.loadPoiSuccess, (state, { poi }) =>
poiAdapter.setAll(poi, { ...state, loaded: true })
),
on(PoiActions.loadPoiFailure, (state, { error }) =>
({ ...state, error })),
on(PoiActions.selectPoi, (state, { poiId }) => ({
...state, selectedId: poiId }))
);
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { PoiActions, PoiEntity, PoiSelectors } from
'@packt/poi';
selectPoi(poi: PoiEntity) {
this.store.dispatch(PoiActions.selectPoi({poiId:
poi.id}));
}
<mat-action-list *ngFor="let poi of pois$ | async">
<button mat-list-item
(click)=»selectPoi(poi)»>{{poi.name}}</button>
</mat-action-list>
To see the new functionality in action, run the application using the Serve option from Nx Console and select a POI from the list. The output of the application should look like the following:
Important note
We are using Google Maps in development mode in this project. For production environments, you should get an API key at https://developers.google.com/maps/gmp-get-started and include it in the Google Maps JavaScript API script that you load in the index.html file such as <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>.
We have now completed all the required features for the portal of our visitors. Well done! Implementing the basic functionality of the visitor portal required interacting with NgRx for managing the global state of our application. The global state was separated into the root state for the application and the feature state for the visitor portal. The latter was used in the visitor library to create Angular components for displaying a POI list and selecting one to view in Google Maps.
In the next section, we will build the administrator portal to get visit statistics for each POI.
The administrator portal will be responsible for displaying traffic visits for each POI using a chart. Traffic will be generated when a visitor visits a POI by clicking its marker on the map. The application will persist visit data in the local storage of the browser. It will keep a record for each visit containing the ID of the POI and the total visits. The administrator portal will consist of the following features:
We will start building the administrator portal by implementing the mechanism of keeping track of visits in the following section.
Our application does not record traffic statistics for POIs yet. Let's see how we can accomplish this task:
<google-map height="100%" width="auto" *ngIf="poi$ |
async as poi" [center]="poi">
<map-marker [position]="poi"></map-marker>
<map-info-window>
<mat-card>
<mat-card-header>
<mat-card-title>{{poi.name}}</mat-card-title>
</mat-card-header>
<img mat-card-image [src]="poi.imgUrl">
<mat-card-content>
<p>{{poi.description}}</p>
</mat-card-content>
</mat-card>
</map-info-window>
</google-map>
The map-info-window component is a pop-up window that displays additional information about the current map marker. It displays the title, image, and description of a POI as an Angular Material card component.
The mat-card component contains a header denoted by the mat-card-header component and an image denoted by the img element with the mat-card-image directive. The mat-card-content component indicates the main content of the card.
import { MatCardModule } from
'@angular/material/card';
@NgModule({
imports: [CommonModule, LayoutModule,
MatToolbarModule, MatButtonModule,
MatSidenavModule, MatIconModule, MatListModule,
RouterModule.forChild([
{ path: '', component: VisitorComponent }
]),
PoiModule,
GoogleMapsModule,
MatCardModule
],
declarations: [VisitorComponent, PoiListComponent,
MapComponent],
})
The MatCardModule class is an Angular Material module that exposes all components that we need for creating a card component.
import { Component, OnInit, ViewChild } from
'@angular/core';
import { Store } from '@ngrx/store';
import { PoiSelectors } from '@packt/poi';
import { MapInfoWindow, MapMarker } from
'@angular/google-maps';
@ViewChild(MapInfoWindow) info: MapInfoWindow |
undefined;
showInfo(marker: MapMarker) {
this.info?.open(marker);
}
In the preceding code, we call the open method of the information window reference, passing the associated map marker as a parameter.
<map-marker #marker="mapMarker" [position]="poi" (mapClick)="showInfo(marker)"></map-marker>
We create the marker template reference variable to get a reference to the mapMarker object and pass it as a parameter in the showInfo method.
We consider that a POI is visited when the visitor clicks on the map marker and the information window appears. Our application will then notify the store of that action to save it in the local storage. Let's create the logic for interacting with the store:
export const visitPoi = createAction(
'[Poi/API] Visit Poi',
props<{ poiId: string | number }>()
)
import { EMPTY } from 'rxjs';
visit$ = createEffect(() =>
this.actions$.pipe(
ofType(PoiActions.visitPoi),
fetch({
run: action => {
const stat = localStorage.getItem('tour-' +
action.poiId);
const total = stat ? Number(stat) + 1 : 1;
localStorage.setItem('tour-' + action.poiId,
total.toString());
return EMPTY;
}
})
)
);
In the preceding code, we fetch the local storage key that begins with the word tour- followed by the POI ID. If there is one, we increment it by one and update the local storage. Otherwise, we initialize it to one.
Tip
In a real case, it would be better to abstract the logic of localStorage in an Angular service that would act as a wrapper over the global localStorage object. We encourage you to create such a service while building this project.
The run method of the effect returns the EMPTY RxJS operator, which causes the observable to complete with no value.
import { PoiActions, PoiSelectors } from '@packt/poi';
showInfo(marker: MapMarker, poiId: string | number) {
this.store.dispatch(PoiActions.visitPoi({ poiId }));
this.info?.open(marker);
}
<map-marker #marker="mapMarker" [position]="poi"
(mapClick)="showInfo(marker, poi.id)"></map-marker>
Our application can now record the visits of each POI and keep them in the local storage of the browser. In the following section, we will create the main component of the administrator portal that leverages visit data.
The administrator portal will display visit statistics on its main component with a graph. We will use the ng2-charts library for visualizing data on a pie chart. Let's see how to add the required functionality in that component:
npm install ng2-charts [email protected]
The preceding command will also install the chart.js library, which is at the core of the ng2-charts library.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminComponent } from './admin.component';
import { RouterModule } from '@angular/router';
import { PoiModule } from '@packt/poi';
import { ChartsModule } from 'ng2-charts';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{ path: '', component: AdminComponent }
]),
PoiModule,
ChartsModule
],
declarations: [
AdminComponent
],
})
export class AdminModule {}
"options": {
"outputPath": "dist/apps/tour",
"index": "apps/tour/src/index.html",
"main": "apps/tour/src/main.ts",
"polyfills": "apps/tour/src/polyfills.ts",
"tsConfig": "apps/tour/tsconfig.app.json",
"assets": ["apps/tour/src/favicon.ico",
"apps/tour/src/assets"],
"styles": [
"./node_modules/@angular/material/prebuilt-
themes/deeppurple-amber.css",
"apps/tour/src/styles.css"
],
"scripts": [],
"allowedCommonJsDependencies": ["chart.js"]
}
The chart.js npm package is a CommonJS module. The Angular CLI displays a warning when using a CommonJS module because it can prevent it from optimizing your application, resulting in large bundle sizes. Setting the allowedCommonJsDependencies option will not display the warning while building the application.
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { PoiActions, PoiEntity, PoiSelectors } from
'@packt/poi';
import { Subscription } from 'rxjs';
export class AdminComponent implements OnInit,
OnDestroy {
private subscription: Subscription | undefined;
constructor(private store: Store) { }
ngOnInit(): void {
this.subscription = this.store.select(
PoiSelectors.getAllPoi).subscribe();
this.store.dispatch(PoiActions.init());
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
}
In the preceding code, we subscribe to the getAllPoi selector manually using a subscription property instead of using an async pipe. In this case, we must also unsubscribe manually in the ngOnDestroy life cycle hook of the component, using the unsubscribe method. If we fail to do so, we may introduce a memory leak in our application.
Now that we have set up the interaction with the store, we can get statistics from the local storage and create our pie chart:
nx generate service admin --project=admin
import { PoiEntity } from '@packt/poi';
getStatistics(pois: PoiEntity[]): number[] {
return pois.map(poi => {
const stat = localStorage.getItem('tour-' +
poi.id) ?? 0;
return +stat;
});
}
In the preceding method, we get the traffic of each POI based on its id property. We convert the stat property to a number by adding the + prefix.
import { Label } from 'ng2-charts';
import { AdminService } from './admin.service';
data: number[] = [];
labels: Label[] = [];
constructor(private store: Store, private
adminService: AdminService) { }
private buildChart(pois: PoiEntity[]) {
this.labels = pois.map(poi => poi.name);
this.data = this.adminService.getStatistics(pois);
}
The graph labels are the titles of the POI, and the data is coming from the getStatistics method of the adminService variable.
ngOnInit(): void {
this.subscription = this.store.select(
PoiSelectors.getAllPoi).subscribe(pois => {
this.buildChart(pois);
});
this.store.dispatch(PoiActions.init());
}
<div class="chart" *ngIf="data.length">
<canvas height="100" baseChart
[data]="data"
[labels]="labels"
chartType="pie">
</canvas>
</div>
In the preceding template, we use the baseChart directive to convert the canvas element to a graph. The graph is set to pie using the chartType property and the data and labels properties from the respective component properties.
If we now run our application using Nx Console, visit some POI from the map, and switch to the http://localhost:4200/admin URL, we should see the following output:
An administrator can now have a complete overview of how each POI is doing from a visit perspective. Our administrator portal has now been completed. The visitor portal can interact with the store and save visit statistics for each POI in the local storage of the browser. The administrator portal can then fetch that data and display it on a pie chart using the ng2-charts library.
In this project, we built an enterprise portal application for visiting POIs on a map and displaying visit statistics for each one. First, we saw how to use Nx Dev Tools to scaffold a new Nx monorepo application. Then, we created two different portals for our application, a visitor and an administrator one. We learned how to use the NgRx library in the visitor portal to maintain and manage the state of our application. Finally, we saw how to use a chart library in the administrator portal for displaying statistics for each POI.
In the next chapter, we will use the Angular CLI to build a UI component library using the Angular CDK.
Let's take a look at a few practice questions:
Here are some links to build upon what we learned in the chapter:
3.135.205.146