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
Enter fullscreen mode Exit fullscreen mode
Generate check-in data access library with NgRx-based 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
Enter fullscreen mode Exit fullscreen mode
Generated file and folder structure for the check-in data access library.

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
Enter fullscreen mode Exit fullscreen mode
Lint and test the check-in data acess library.

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
Enter fullscreen mode Exit fullscreen mode
Generate check-in feature shell library.

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
Enter fullscreen mode Exit fullscreen mode
Generated file and folder structure for the check-in feature shell library.

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 {}
Enter fullscreen mode Exit fullscreen mode
Check-in feature shell module.

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 {}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
Generate the check-in desktop application using its feature shell library.

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 {}
Enter fullscreen mode Exit fullscreen mode
App module of the check-in desktop application.

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>
Enter fullscreen mode Exit fullscreen mode
Check-in desktop app component template.

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'); // ?
  });
});
Enter fullscreen mode Exit fullscreen mode
Test suite for the check-in desktop app component.

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));
Enter fullscreen mode Exit fullscreen mode
The check-in desktop application main file.

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"
                }
              ]
            }
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
File replacements configured to use the shared environments library for the check-in desktop application.

We're looking good!

ng run check-in-desktop:lint

ng run check-in-desktop:test --watch=false
Enter fullscreen mode Exit fullscreen mode
Lint and test the check-in desktop application.

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>; // ?
  }
}
Enter fullscreen mode Exit fullscreen mode
App page object for the booking desktop application.

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
Enter fullscreen mode Exit fullscreen mode
Lint and run the end-to-end test suite of the check-in desktop application.

The complete end-to-end test suite passes both linting and execution checks.

ng run check-in-desktop:serve
Enter fullscreen mode Exit fullscreen mode
Start the development server for the check-in desktop application.

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
Enter fullscreen mode Exit fullscreen mode
Generate the check-in desktop application using its feature shell library.

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
Enter fullscreen mode Exit fullscreen mode
The file and folder structure generated for the mobile check-in application.

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
Enter fullscreen mode Exit fullscreen mode
All check-in application and end-to-end testing projects are ready.

Conclusion

To launch the mobile check-in app, execute ng run check-in-mobile:serve.

The mobile check-in application with the NgRx Store Devtools open.

The mobile check-in application with the NgRx Store Devtools open.

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
Enter fullscreen mode Exit fullscreen mode
Workspace folder structure after Part 4.

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.