Original cover photo by Edgar Chaparro on Unsplash.
Original publication date: 2020-05-19.
This tutorial is part of the Angular Architectural Patterns series.
In Part 3 of this tutorial, we created two feature libraries with routed components and hooked them up to the check-in feature shell library. We used the generate project tool to create the mobile check-in application and created a mobile-specific template for the flight search component.
In this part, we're going to create two workspace libraries in the check-in domain, the check-in data access library and the check-in feature shell library. We register data access in the feature shell library, create the check-in desktop application and hook up the feature shell Angular module. After reviewing and verifying how much of this is automated by our generate project tool, we quickly generate the mobile check-in application.
Check-in data access library
Let's move on to the check-in domain. This time, we'll start with the data access library. I added an extra option called --with-state
to generate a feature store and effects in the +state
folder.
npm run generate-project -- library data-access --scope=check-in --npm-scope=nrwl-airlines --with-state
# or
yarn generate-project library data-access --scope=check-in --npm-scope=nrwl-airlines --with-state
This generates the following file and folder structure.
libs/check-in/data-access
├── src
│ ├── lib
│ │ ├── +state
│ │ │ ├── check-in.actions.spec.ts
│ │ │ ├── check-in.actions.ts
│ │ │ ├── check-in.effects.spec.ts
│ │ │ ├── check-in.effects.ts
│ │ │ ├── check-in.reducer.spec.ts
│ │ │ ├── check-in.reducer.ts
│ │ │ ├── check-in.selectors.spec.ts
│ │ │ └── check-in.selectors.ts
│ │ ├── check-in-data-access.module.spec.ts
│ │ └── check-in-data-access.module.ts
│ ├── index.ts
│ └── test.ts
├── README.md
├── karma.conf.js
├── tsconfig.lib.json
├── tsconfig.spec.json
└── tslint.json
How sweet is this? One command and we've got a project-specific data access library set up with project configuration, path mappings, feature store, and feature effects.
Lint and test the project. Correct any errors like you did earlier.
ng run check-in-data-access:lint
ng run check-in-data-access:test --watch=false
Check-in feature shell library
We use the generate project tool to generate the check-in feature shell.
npm run generate-project -- library feature feature-shell --scope=check-in --npm-scope=nrwl-airlines
# or
yarn generate-project library feature feature-shell --scope=check-in --npm-scope=nrwl-airlines
Everything's now set up for us.
libs/check-in/feature-shell
├── src
│ ├── lib
│ │ ├── shell
│ │ │ ├── shell.component.css
│ │ │ ├── shell.component.html
│ │ │ ├── shell.component.spec.ts
│ │ │ └── shell.component.ts
│ │ ├── check-in-feature-shell.module.spec.ts
│ │ └── check-in-feature-shell.module.ts
│ ├── index.ts
│ └── test.ts
├── README.md
├── karma.conf.js
├── tsconfig.lib.json
├── tsconfig.spec.json
└── tslint.json
Let's take a quick look at the check-in feature shell Angular module.
// check-in-feature-shell.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ShellComponent } from './shell/shell.component';
const routes: Routes = [
{
path: '',
component: ShellComponent,
children: [],
},
];
@NgModule({
declarations: [ShellComponent],
exports: [RouterModule],
imports: [RouterModule.forRoot(routes)],
})
export class CheckInFeatureShellModule {}
We need to register the shared data access and check-in data access Angular modules.
// check-in-feature-shell.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CheckInDataAccessModule } from '@nrwl-airlines/check-in/data-access';
import { SharedDataAccessModule } from '@nrwl-airlines/shared/data-access';
import { ShellComponent } from './shell/shell.component';
const routes: Routes = [
{
path: '',
component: ShellComponent,
children: [],
},
];
@NgModule({
declarations: [ShellComponent],
exports: [RouterModule],
imports: [
RouterModule.forRoot(routes),
SharedDataAccessModule, // ?
CheckInDataAccessModule, // ?
],
})
export class CheckInFeatureShellModule {}
Great! Since we don't have any features to add to the check-in application yet, we can move on to the next project for now.
Check-in desktop application
It's time to create the first check-in application, the desktop web app. I've added support for feature shells in the generate project tool. If a feature shell library exists in the same scope, the application will be generated with the same changes as we made earlier in this article. We just need to make sure to use the --npm-scope
parameter.
npm run generate-project -- application check-in-desktop --scope=check-in --grouping-folder=check-in --npm-scope=nrwl-airlines
# or
yarn generate-project application check-in-desktop --scope=check-in --grouping-folder=check-in --npm-scope=nrwl-airlines
Let's make sure it worked. Open up the app module of the new application project.
// apps/check-in/check-in-desktop/src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CheckInFeatureShellModule } from '@nrwl-airlines/check-in/feature-shell';
import { AppComponent } from './app.component';
@NgModule({
bootstrap: [AppComponent],
declarations: [AppComponent],
imports: [
BrowserModule,
CheckInFeatureShellModule, // ?
],
})
export class AppModule {}
The app component is generated with the same template as our other apps.
<!-- apps/check-in/check-in-desktop/src/app/app.component.html -->
<h1>{{title}}</h1>
<router-outlet></router-outlet>
As seen in the app module, we eagerly load the check-in feature shell module which routes to its shell component as we saw in the previous section.
The changes to the app component are reflected in its test suite.
// apps/check-in/check-in-desktop/src/app/app.component.spec.ts
import { TestBed, async } from '@angular/core/testing';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [
RouterModule.forRoot([]), // ?
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'check-in-desktop'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('check-in-desktop');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('h1').textContent) // ?
.toContain('check-in-desktop'); // ?
});
});
Our Angular testing module adds the router module to make us able to render the app component's template. The heading selector and content now reflects the simple template.
The final change that we want to check is the usage of the shared environments workspace library in the main file.
// apps/check-in/check-in-desktop/src/main.ts
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from '@nrwl-airlines/shared/environments'; // ?
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
The generate project tool detects a shared environments library, changes the import statement in the main file and additionally delete the src/environments
folder and files which are usually generated as part of an application project.
Now, let's make sure that we're using the shared environments library in the fileReplacements
option of our application project's configuration.
{
"//": "angular.json",
"projects": {
"check-in-desktop": {
"architect": {
"build": {
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "libs/shared/environments/src/lib/environment.ts",
"with": "libs/shared/environments/src/lib/environment.prod.ts"
}
]
}
}
}
}
}
}
}
We're looking good!
ng run check-in-desktop:lint
ng run check-in-desktop:test --watch=false
Lint checks and the unit test suite run as expected.
Let's verify the changes to the end-to-end test suites.
// apps/check-in/check-in-desktop-e2e/src/app.po.ts
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('check-in-root h1')).getText() as Promise<string>; // ?
}
}
For some reason, Angular only leaves out the last name of the project-name for the root element. In this case, event though the application project's name is check-in-desktop
, the root element name is check-in-root
. The generate project tool respects this.
In the booking apps, the root element name is booking-root
rather than booking-desktop-root
and booking-mobile-root
. This can also be seen in the index.html
file of an application project generated using the Angular CLI.
ng run check-in-desktop-e2e:lint
ng run check-in-desktop-e2e:e2e
The end-to-end test suite is linted and tested successfully.
ng run check-in-desktop:serve
Finally, start the application and verify that it renders the title. As no feature libraries have been added yet, only the title is shown, but no errors are thrown.
Mobile check-in application
The final application project is the mobile check-in web app.
npm run generate-project -- application check-in-mobile --scope=check-in --grouping-folder=check-in --npm-scope=nrwl-airlines
# or
yarn generate-project application check-in-mobile --scope=check-in --grouping-folder=check-in --npm-scope=nrwl-airlines
The project is generated exactly as described in the previous section.
Let's review the file and folder structure that is generated for us.
apps/check-in
├── check-in-mobile
│ ├── src
│ │ ├── app
│ │ │ ├── app.component.css
│ │ │ ├── app.component.html
│ │ │ ├── app.component.spec.ts
│ │ │ ├── app.component.ts
│ │ │ └── app.module.ts
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── polyfills.ts
│ │ ├── styles.css
│ │ └── test.ts
│ ├── browserslist
│ ├── karma.conf.js
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── tslint.json
└── check-in-mobile-e2e
├── src
│ ├── app.e2e-spec.ts
│ └── app.po.ts
├── protractor.conf.js
└── tsconfig.json
With both check-in applications in place, we have the following project folder structure.
nrwl-airlines
└── apps
└── check-in
├── check-in-desktop
├── check-in-desktop-e2e
├── check-in-mobile
└── check-in-mobile-e2e
Conclusion
Start the mobile check-in application by running the ng run check-in-mobile:serve
command.
At this point, our workspace folder structure looks like the following figure.
nrwl-airlines
├── apps
│ ├── booking
│ │ ├── booking-desktop
│ │ ├── booking-desktop-e2e
│ │ ├── booking-mobile
│ │ └── booking-mobile-e2e
│ └── check-in
│ ├── check-in-desktop
│ ├── check-in-desktop-e2e
│ ├── check-in-mobile
│ └── check-in-mobile-e2e
├── libs
│ ├── booking
│ │ ├── data-access
│ │ ├── feature-flight-search
│ │ ├── feature-passenger-info
│ │ └── feature-shell
│ ├── check-in
│ │ ├── data-access
│ │ └── feature-shell
│ └── shared
│ ├── data-access
│ └── environments
└── tools
In this part of the tutorial, we used our generate project tool to generate the check-in data access library. This time, we added the --with-state
parameter to automate the generation of feature state that we generate manually in Part 2.
Next, we generated the check-in feature shell library project and hooked up shared data access as well as check-in data access by importing their Angular modules in the check-in feature shell Angular module.
With data access in place, we generated the check-in desktop application and end-to-end test projects. We went through the steps that the generate project tool does for us. Changes we did manually in Parts 1 and 2.
Finally, we generated the mobile check-in application and end-to-end test projects.
We'll finish the monorepo in Part 5 where we create the seatmap domain and the shared buttons UI library as well as the shared formatting utilities library. We'll wrap up the tutorial series by looking at what other features Nx offers us that Angular CLI doesn't provide out-of-the-box.
Resources
For the impatient programmer, the full solution is in the LayZeeDK/ngx-nrwl-airlines-workspace
GitHub repository.
Top comments (0)