Cover photo originally by Edgar Chaparro on Unsplash.
First published: 2020-05-19.
This walkthrough belongs to the Angular Architectural Patterns series.
During Part 3, we set up two feature libraries that included routed components, and we connected them to the check-in feature shell library. The mobile check-in application was generated with the project tool, and we also created a mobile-only template for the flight search component.
Now, in this section, we'll set up two libraries inside the check-in domain: one for data access and one for the feature shell. We'll wire up the data access library within the feature shell, build the check-in desktop app, and connect the feature shell's Angular module. Then, we'll check how much of this workflow is handled automatically by the generate project tool, and proceed to quickly scaffold the mobile check-in application.
Check-in data access library
Next, we turn to the check-in domain, beginning with the data access library. A new flag, --with-state, has been added to produce a feature store and effects within the +state directory.
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 is where the file-and-folder layout below gets created.
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
Quite the payoff: a single command generates a data access library tailored to the project, complete with its own configuration, path mappings, feature store, and feature effects.
Run linting and tests for the project. Fix any issues that surface, just as before.
ng run check-in-data-access:lint
ng run check-in-data-access:test --watch=false
Check-in feature shell library
The check-in feature shell is created via the generate project tool.
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
Here’s a brief rundown of the Angular module that serves as the feature shell for check-in functionality.
// 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 {}
Both the shared data access module and the check-in data access module must be registered in the project.
// 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 {}
Excellent! With no new features currently queued for the check-in app, we're free to shift focus to the upcoming task.
Check-in desktop application
Now we build the first check-in app, the web-based desktop version. The generate project tool has been extended to handle feature shells. Whenever a feature shell library is present within the same scope, the generated application inherits all the modifications we applied earlier in this guide. The only requirement is passing the --npm-scope flag.
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
Time to verify the result. Navigate to the app module inside the freshly generated 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 structure of the app component is identical to the templates found across our other applications.
<!-- apps/check-in/check-in-desktop/src/app/app.component.html -->
<h1>{{title}}</h1>
<router-outlet></router-outlet>
The app module, as we observed earlier, eagerly imports the check-in feature shell module, which in turn directs routes to its corresponding shell component—a pattern consistent with the previous discussion.
Modifications made to the app component are mirrored in its associated 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'); // ?
});
});
To enable rendering of the app component's template, our Angular testing module incorporates the router module. The heading selector and its content mirror the basic template at this point.
We still need to verify that the shared environments workspace library is being used in the main file—that's the last modification to inspect.
// 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 flag for the shared environments library is picked up by the project generation utility, which then swaps the import path in the entry point and removes the convention-generated src/environments directory plus its contents.
Next, let's verify that the fileReplacements setting within our application's build configuration references this same shared environments library.
{
"//": "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
The linting process and the unit tests both complete without issues.
Now, let's validate what we did 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>; // ?
}
}
The root element name doesn't follow the full project name in Angular—only the final portion is dropped. Specifically, although the app project is named check-in-desktop, the root element comes out as check-in-root. The generation tool adheres to this convention.
For the booking projects, you'll notice booking-root as the root element, not booking-desktop-root or booking-mobile-root. If you peek at the index.html file in any app generated by the Angular CLI, this pattern shows up there too.
ng run check-in-desktop-e2e:lint
ng run check-in-desktop-e2e:e2e
The complete end-to-end test suite passes both linting and execution checks.
ng run check-in-desktop:serve
Once the app is up and running, confirm it displays its title as expected. Since no feature libraries have been created at this stage, you will only see the heading—no error messages or unexpected issues should appear. The linked source provides further reference.
Mobile check-in application
The last project to build is the mobile check-in web application.
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 steps for generating the project follow precisely the pattern laid out in the part before this one.
Here’s what the resulting directory hierarchy looks like when examined.
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
Now that both check-in applications are set up, the workspace’s project layout looks like this.
nrwl-airlines
└── apps
└── check-in
├── check-in-desktop
├── check-in-desktop-e2e
├── check-in-mobile
└── check-in-mobile-e2e
Conclusion
To launch the mobile check-in app, execute ng run check-in-mobile:serve.
The structure of our workspace directory now matches what you see below.
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
During this section of the walkthrough, the check-in data access library was created using our generate project tool. For this task, the --with-state flag was supplied, which automatically sets up feature state—something we had to write by hand back in Part 2 for the other modules.
From there, we scaffolded the check-in feature shell library project. Its Angular module serves as the integration point, importing both the shared data access and check-in data access modules to wire them together.
Now that data access was squared away, the check-in desktop application and its corresponding end-to-end test project were generated. This time we walked through the internal steps of the generate project tool—steps that mirror the manual modifications we made across Parts 1 and 2.
For the finishing touches, the mobile check-in app and its own end-to-end test project were created as well.
Part 5 closes out the monorepo. That involves building the seatmap domain, a shared buttons UI library, and a formatting utilities library. We'll also wrap up the series by discussing the additional capabilities Nx brings to the table beyond what the Angular CLI gives us by default.
Resources
If you're in a hurry and want to skip ahead, the finished codebase can be found in the LayZeeDK/ngx-nrwl-airlines-workspace repository on GitHub.

