An introduction to Angular CLI Builders

In this article explains how CLI builders integrate with the workspace configuration file, and shows how you can create your own builder.

A number of Angular CLI commands run a complex process on your code, such as linting, building, or testing. The commands use an internal tool called Architect to run CLI builders, which apply another tool to accomplish the desired task.

With Angular version 8, the CLI Builder API is stable and available to developers who want to customize the Angular CLI by adding or modifying commands. For example, you could supply a builder to perform an entirely new task, or to change which third-party tool is used by an existing command.

You can find the code from the examples used here in this GitHub repository.## CLI builders

The internal Architect tool delegates work to handler functions called builders. A builder handler function receives two arguments; a set of input options (a JSON object), and a context (a BuilderContext object).

The separation of concerns here is the same as with schematics, which are used for other CLI commands that touch your code (such as ng generate).

  • Options are given by the CLI user, context is provided by and provides access to the CLI Builder API, and the developer provides the behavior.
  • The BuilderContext object provides access to the scheduling method, BuilderContext.scheduleTarget(). The scheduler executes the builder handler function with a given target configuration.

The builder handler function can be synchronous (return a value) or asynchronous (return a Promise), or it can watch and return multiple values (return an Observable). The return value or values must always be of type BuilderOutput. This object contains a Boolean success field and an optional error field that can contain an error message.

Angular provides some builders that are used by the CLI for commands such as ng build, ng test, and ng lint. Default target configurations for these and other built-in CLI builders can be found (and customized) in the “architect” section of the workspace configuration file, angular.json. You can also extend and customize Angular by creating your own builders, which you can run using the <a href="https://angular.io/cli/run" target="_blank">ng run</a> CLI command.

Builder project structure

A builder resides in a “project” folder that is similar in structure to an Angular workspace, with global configuration files at the top level, and more specific configuration in a source folder with the code files that define the behavior. For example, your myBuilder folder could contain the following files.

You can publish the builder to npm (see Publishing your Library). If you publish it as @example/my-builder, you can install it using the following command.

      
npm install @example/my-builder
    

Creating a builder

As an example, let’s create a builder that executes a shell command. To create a builder, use the createBuilder() CLI Builder function, and return a BuilderOutput object.

      
import { BuilderOutput, createBuilder } from '@angular-devkit/architect';

 

export default createBuilder(_commandBuilder);

 

function _commandBuilder(

  options: JsonObject,

  context: BuilderContext,

  ): Promise<BuilderOutput> {

  ...

}
    

Now let’s add some logic to it. The following code retrieves the command and arguments from the user options, spawns the new process, and waits for the process to finish. If the process is successful (returns a code of 0), it resolves the return value.

      
import { BuilderOutput, createBuilder } from '@angular-devkit/architect';

import * as childProcess from 'child_process';

 

export default createBuilder(_commandBuilder);

 

function _commandBuilder(

  options: JsonObject,

  context: BuilderContext,

): Promise<BuilderOutput> {

  const child = childProcess.spawn(options.command, options.args);

  return new Promise<BuilderOutput>(resolve => {

    child.on('close', code => {

      resolve({success: code === 0});

    });

  });

}
    

Handling output

By default, the spawn() method outputs everything to the process standard output and error. To make it easier to test and debug, we can forward the output to the CLI Builder logger instead. This also allows the builder itself to be executed in a separate process, even if the standard output and error are deactivated (as in an Electron app).

We can retrieve a Logger instance from the context.

      
import { BuilderOutput, createBuilder, BuilderContext } from '@angular-devkit/architect';

import * as childProcess from 'child_process';

 

export default createBuilder(_commandBuilder);

 

function _commandBuilder(

  options: JsonObject,

  context: BuilderContext,

): Promise<BuilderOutput> {

  const child = childProcess.spawn(options.command, options.args, {stdio: 'pipe'});

  child.stdout.on('data', (data) => {

    context.logger.info(data.toString());

  });

  child.stderr.on('data', (data) => {

    context.logger.error(data.toString());

  });

 

  return new Promise<BuilderOutput>(resolve => {

    child.on('close', code => {

      resolve({success: code === 0});

    });

  });

}
    

Progress and status reporting

The CLI Builder API includes progress and status reporting tools, which can provide hints for certain functions and interfaces.

To report progress, use the BuilderContext.reportProgress() method, which takes a current value, (optional) total, and status string as arguments. The total can be any number; for example, if you know how many files you have to process, the total could be the number of files, and current should be the number processed so far. The status string is unmodified unless you pass in a new string value.

You can see an example of how the tslint builder reports progress.

In our example, the shell command either finishes or is still executing, so there’s no need for a progress report, but we can report status so that a parent builder that called our builder would know what’s going on. Use the BuilderContext.reportStatus() method to generate a status string of any length. (Note that there’s no guarantee that a long string will be shown entirely; it could be cut to fit the UI that displays it.) Pass an empty string to remove the status.

      
import { BuilderOutput, createBuilder, BuilderContext } from '@angular-devkit/architect';

import * as childProcess from 'child_process';

 

export default createBuilder(_commandBuilder);

 

function _commandBuilder(

  options: JsonObject,

  context: BuilderContext,

): Promise<BuilderOutput> {

  context.reportStatus(`Executing "${options.command}"...`);

  const child = childProcess.spawn(options.command, options.args, {stdio: 'pipe'});

 

  child.stdout.on('data', (data) => {

    context.logger.info(data.toString());

  });

  child.stderr.on('data', (data) => {

    context.logger.error(data.toString());

  });

 

  return new Promise<BuilderOutput>(resolve => {

    context.reportStatus(`Done.`);

    child.on('close', code => {

      resolve({success: code === 0});

    });

  });

}
    

Builder input

You can invoke a builder indirectly through a CLI command, or directly with the Angular CLI ng run command. In either case, you must provide required inputs, but can allow other inputs to default to values that are pre-configured for a specific target, provide a pre-defined, named override configuration, and provide further override option values on the command line.

Input validation

You define builder inputs in a JSON schema associated with that builder. The Architect tool collects the resolved input values into an options object, and validates their types against the schema before passing them to the builder function. (The Schematics library does the same kind of validation of user input).

For our example builder, we expect the options value to be a JsonObject with two keys: a command that is a string, and an args array of string values.

We can provide the following schema for type validation of these values.

      
{

  "$schema": "http://json-schema.org/schema",

  "type": "object",

  "properties": {

    "command": {

      "type": "string"

    },

    "args": {

      "type": "array",

      "items": {

        "type": "string"

      }

    }

  }

}
    

This is a very simple example, but the use of a schema for validation can be very powerful. For more information, see the JSON schemas website.

To link our builder implementation with its schema and name, we need to create a builder definition file, which we can point to in package.json.

Create a file named builders.json file that looks like this.

{

  "builders": {

    "command": {

      "implementation": "./command",

      "schema": "./command/schema.json",

      "description": "Runs any command line in the operating system."

    }

  }

}
    

In the package.json file, add a builders key that tells the Architect tool where to find our builder definition file.

{

  "name": "@example/command-runner",

  "version": "1.0.0",

  "description": "Builder for Command Runner",

  "builders": "builders.json",

  "devDependencies": {

    "@angular-devkit/architect": "^1.0.0"

  }

}
    

The official name of our builder is now @example/command-runner:command. The first part of this is the package name (resolved using node resolution), and the second part is the builder name (resolved using the builder.json file).

Using one of our options is very straightforward, we did this in the previous section when we accessed options.command.

      
context.reportStatus(`Executing "${options.command}"...`);

const child = childProcess.spawn(options.command, options.args, { stdio: 'pipe' });
    

Target configuration

A builder must have a defined target that associates it with a specific input configuration and project.

Targets are defined in the angular.json workspace configuration file. A target specifies the builder to use, its default options configuration, and named alternative configurations. The Architect tool uses the target definition to resolve input options for a given run.

The angular.json file has a section for each project, and the “architect” section of each project configures targets for builders used by CLI commands such as ‘build’, ‘test’, and ‘lint’. By default, for example, the build command runs the builder @angular-devkit/build-angular:<a href="https://angular.io/api/animations/browser" target="_blank">browser</a> to perform the build task, and passes in default option values as specified for the build target in angular.json.

{

  "myApp": {

    ...

    "architect": {

        "build": {

             "builder": "@angular-devkit/build-angular:browser",

             "options": {

            "outputPath": "dist/myApp",

            "index": "src/index.html",

        ...

   },

          "configurations": {

            "production": {

                 "fileReplacements": [

                    {

                         "replace": "src/environments/environment.ts",

                         "with": "src/environments/environment.prod.ts"

                    }

                 ],

               "optimization": true,

               "outputHashing": "all",

      ...

            }

             }

        },

       ...

The command passes the builder the set of default options specified in the “options” section. If you pass the --configuration=production flag, it uses the override values specified in the production alternative configuration. You can specify further option overrides individually on the command line. You might also add more alternative configurations to the build target, to define other environments such as stage or qa.

Target strings

The generic ng run CLI command takes as its first argument a target string of the form project:target[:configuration].

  • project: The name of the Angular CLI project that the target is associated with.
  • target: A named builder configuration from the architect section of the angular.json file.
  • configuration: (optional) The name of a specific configuration override for the given target, as defined in the angular.json file.

If your builder calls another builder, it may need to read a passed target string. You can parse this string into an object by using the targetFromTargetString() utility function from @angular-devkit/architect.

Schedule and run

Architect runs builders asynchronously. To invoke a builder, you schedule a task to be run when all configuration resolution is complete.

The builder function is not executed until the scheduler returns a BuilderRun control object. The CLI typically schedules tasks by calling the BuilderContext.scheduleTarget() function, and then resolves input options using the target definition in the angular.json file.

Architect resolves input options for a given target by taking the default options object, then overwriting values from the configuration used (if any), then further overwriting values from the overrides object passed to BuilderContext.scheduleTarget(). For the Angular CLI, the overrides object is built from command line arguments.

Architect validates the resulting options values against the schema of the builder. If inputs are valid, Architect creates the context and executes the builder.

For more information see Workspace Configuration.

You can also invoke a builder directly from another builder or test by calling BuilderContext.scheduleBuilder(). You pass an options object directly to the method, and those option values are validated against the schema of the builder without further adjustment.

Only the BuilderContext.scheduleTarget() method resolves the configuration and overrides through the angular.json file.

Default architect configuration

Let’s create a simple angular.json file that puts target configurations into context.

We can publish the builder to npm (see Publishing your Library), and install it using the following command:

      
npm install @example/command-runner
    

If we create a new project with ng new builder-test, the generated angular.json file looks something like this, with only default builder configurations.

{

  // ...

  "projects": {

    // ...

    "builder-test": {

      // ...

      "architect": {

        // ...

        "build": {

          "builder": "@angular-devkit/build-angular:browser",

          "options": {

            // ... more options...

            "outputPath": "dist/builder-test",

            "index": "src/index.html",

            "main": "src/main.ts",

            "polyfills": "src/polyfills.ts",

            "tsConfig": "src/tsconfig.app.json"

          },

          "configurations": {

            "production": {

              // ... more options...

              "optimization": true,

              "aot": true,

              "buildOptimizer": true

            }

          }

        }

      }

    }

  }

  // ...

}

Adding a target

Let’s add a new target that will run our builder to execute a particular command. This target will tell the builder to run touch on a file, in order to update its modified date.

We need to update the angular.json file to add a target for this builder to the “architect” section of our new project.

  • We’ll add a new target section to the “architect” object for our project.
  • The target named “touch” uses our builder, which we published to @example/command-runner. (See Publishing your Library)
  • The options object provides default values for the two inputs that we defined; command, which is the Unix command to execute, and args, an array that contains the file to operate on.
  • The configurations key is optional, we’ll leave it out for now.
      
{

  "projects": {

    "builder-test": {

      "architect": {

        "builder-test": {

          "touch": {

            "builder": "@example/command-runner:command",

            "options": {

              "command": "touch",

              "args": [

                "src/main.ts"

              ]

            }

          }

        },

        "build": {

          "builder": "@angular-devkit/build-angular:browser",

          "options": {

            "outputPath": "dist/builder-test",

            "index": "src/index.html",

            "main": "src/main.ts",

            "polyfills": "src/polyfills.ts",

            "tsConfig": "src/tsconfig.app.json"

          },

          "configurations": {

            "production": {

              "fileReplacements": [

                {

                  "replace": "src/environments/environment.ts",

                  "with": "src/environments/environment.prod.ts"

                }

              ],

              "optimization": true,

              "aot": true,

              "buildOptimizer": true

            }

          }

        }

      }

    }

  }

}

Running the builder

To run our builder with the new target’s default configuration, use the following CLI command in a Linux shell.

      
ng run builder-test:touch
    

This will run the touch command on the src/main.ts file.

You can use command-line arguments to override the configured defaults. For example, to run with a different command value, use the following CLI command.

      
ng run builder-test:touch --command=ls
    

This will call the ls command instead of the touch command. Because we did not override the args option, it will list information about the src/main.ts file (the default value provided for the target).

Testing a builder

Use integration testing for your builder, so that you can use the Architect scheduler to create a context, as in this example.

  • In the builder source directory, we have created a new test file index.spec.ts. The code creates new instances of JsonSchemaRegistry (for schema validation), TestingArchitectHost (an in-memory implementation of ArchitectHost), and Architect.
  • We’ve added a builders.json file next to the builder’s <a href="https://github.com/mgechev/cli-builders-demo/blob/master/command-builder/builders.json" target="_blank">package.json</a> file, and modified the package file to point to it.

Here’s an example of a test that runs the command builder. The test uses the builder to run the ls command, then validates that it ran successfully and listed the proper files.

import { Architect, ArchitectHost } from '@angular-devkit/architect';

import { TestingArchitectHost } from '@angular-devkit/architect/testing';

// Our builder forwards the STDOUT of the command to the logger.

import { logging, schema } from '@angular-devkit/core';

 

describe('Command Runner Builder', () => {

  let architect: Architect;

  let architectHost: ArchitectHost;

 

  beforeEach(async () => {

    const registry = new schema.CoreSchemaRegistry();

    registry.addPostTransform(schema.transforms.addUndefinedDefaults);

 

    // TestingArchitectHost() takes workspace and current directories.

    // Since we don't use those, both are the same in this case.

    architectHost = new TestingArchitectHost(__dirname, __dirname);

    architect = new Architect(architectHost, registry);

 

    // This will either take a Node package name, or a path to the directory

    // for the package.json file.

    await architectHost.addBuilderFromPackage('..');

  });

 

  // This might not work in Windows.

  it('can run ls', async () => {

    // Create a logger that keeps an array of all messages that were logged.

    const logger = new logging.Logger('');

    const logs = [];

    logger.subscribe(ev => logs.push(ev.message));

 

    // A "run" can have multiple outputs, and contains progress information.

    const run = await architect.scheduleBuilder('@example/command-runner:command', {

      command: 'ls',

      args: [__dirname],

    }, { logger });  // We pass the logger for checking later.

 

    // The "result" member (of type BuilderOutput) is the next output.

    const output = await run.result;

 

    // Stop the builder from running. This stops Architect from keeping

    // the builder-associated states in memory, since builders keep waiting

    // to be scheduled.

    await run.stop();

 

    // Expect that it succeeded.

    expect(output.success).toBe(true);

 

    // Expect that this file was listed. It should be since we're running

    // `ls $__dirname`.

    expect(logs).toContain('index.spec.ts');

  });

});

When running this test in your repo, you need the <a href="https://github.com/TypeStrong/ts-node" target="_blank">ts-node</a> package. You can avoid this by renaming index.spec.ts to index.spec.js.

Watch mode

Architect expects builders to run once (by default) and return. This behavior is not entirely compatible with a builder that watches for changes (like Webpack, for example). Architect can support watch mode, but there are some things to look out for.

  • To be used with watch mode, a builder handler function should return an Observable. Architect subscribes to the Observable until it completes and might reuse it if the builder is scheduled again with the same arguments.
  • The builder should always emit a BuilderOutput object after each execution. Once it’s been executed, it can enter a watch mode, to be triggered by an external event. If an event triggers it to restart, the builder should execute the BuilderContext.reportRunning() function to tell Architect that it is running again. This prevents Architect from stopping the builder if another run is scheduled.

When your builder calls BuilderRun.stop() to exit watch mode, Architect unsubscribes from the builder’s Observable and calls the builder’s teardown logic to clean up. (This behavior also allows for long running builds to be stopped and cleaned up.)

In general, if your builder is watching an external event, you should separate your run into three phases.

  1. Running For example, webpack compiles. This ends when webpack finishes and your builder emits a BuilderOutput object.
  2. Watching Between two runs, watch an external event stream. For example, webpack watches the file system for any changes. This ends when webpack restarts building, and BuilderContext.reportRunning() is called. This goes back to step 1.
  3. Completion Either the task is fully completed (for example, webpack was supposed to run a number of times), or the builder run was stopped (using BuilderRun.stop()). Your teardown logic is executed, and Architect unsubscribes from your builder’s Observable.

Summary

The CLI Builder API provides a new way of changing the behavior of the Angular CLI by using builders to execute custom logic.

  • Builders can be synchronous or asynchronous, execute once or watch for external events, and can schedule other builders or targets.
  • Builders have option defaults specified in the angular.json configuration file, which can be overwritten by an alternate configuration for the target, and further overwritten by command line flags.
  • We recommend that you use integration tests to test Architect builders. You can use unit tests to validate the logic that the builder executes.
  • If your builder returns an Observable, it should clean up in the teardown logic of that Observable.

#angular #web-development

What is GEEK

Buddha Community

An introduction to Angular CLI Builders
Christa  Stehr

Christa Stehr

1598940617

Install Angular - Angular Environment Setup Process

Angular is a TypeScript based framework that works in synchronization with HTML, CSS, and JavaScript. To work with angular, domain knowledge of these 3 is required.

  1. Installing Node.js and npm
  2. Installing Angular CLI
  3. Creating workspace
  4. Deploying your First App

In this article, you will get to know about the Angular Environment setup process. After reading this article, you will be able to install, setup, create, and launch your own application in Angular. So let’s start!!!

Angular environment setup

Install Angular in Easy Steps

For Installing Angular on your Machine, there are 2 prerequisites:

  • Node.js
  • npm Package Manager
Node.js

First you need to have Node.js installed as Angular require current, active LTS or maintenance LTS version of Node.js

Download and Install Node.js version suitable for your machine’s operating system.

Npm Package Manager

Angular, Angular CLI and Angular applications are dependent on npm packages. By installing Node.js, you have automatically installed the npm Package manager which will be the base for installing angular in your system. To check the presence of npm client and Angular version check of npm client, run this command:

  1. npm -v

Installing Angular CLI

  • Open Terminal/Command Prompt
  • To install Angular CLI, run the below command:
  1. npm install -g @angular/cli

installing angular CLI

· After executing the command, Angular CLI will get installed within some time. You can check it using the following command

  1. ng --version

Workspace Creation

Now as your Angular CLI is installed, you need to create a workspace to work upon your application. Methods for it are:

  • Using CLI
  • Using Visual Studio Code
1. Using CLI

To create a workspace:

  • Navigate to the desired directory where you want to create your workspace using cd command in the Terminal/Command prompt
  • Then in the directory write this command on your terminal and provide the name of the app which you want to create. In my case I have mentioned DataFlair:
  1. Ng new YourAppName

create angular workspace

  • After running this command, it will prompt you to select from various options about the CSS and other functionalities.

angular CSS options

  • To leave everything to default, simply press the Enter or the Return key.

angular setup

#angular tutorials #angular cli install #angular environment setup #angular version check #download angular #install angular #install angular cli

Marcelle  Smith

Marcelle Smith

1590872280

Angular CLI Builders

Builders API is released with Angular 8, and it offers a functionality where you can override commands as ng build,ng test and ng lint . Not to be confused with Angular Schematics here, by using which you can add your own commands for ng generate commands or provide support for ng add .

In this blog post, we will discuss Angular CLI Builders, in case you are following Angular, with version 8 Builders are talked about a lot. So let’s see how we can use the Builders API.

#angular #angular8 #typescript #cli #builders

Roberta  Ward

Roberta Ward

1595333359

Tutorial: Nx-style monorepo workspace with Angular CLI: Part 5

In the final part of this tutorial, we create the seatmap data access, seat listing feature, shared buttons UI, and shared formatting utilities library. Finally, we compare our approach with the full Nx toolchain.

This tutorial is part of the Angular Architectural Patterns series.

In Part 4 of this tutorial, we used our generate project tool to create the check-in data access library, the check-in feature shell library, the check-in desktop application, and the mobile check-in application. We hooked everything up and reviewed how much was automated by our tool.

In this part of the tutorial, we’re going to create the seatmap data access library with NgRx feature state. We then created the seat listing feature library and hooked it up to all applications with routing. Finally, we created the shared buttons UI library and the shared formatting utilities library which we used in the seat listing component.

THIS AD MAKES CONTENT FREE. HIDE

Seatmap data access library#

The shared seatmap feature has its own data access library. This is where we would add data services and application state management specific to the seatmap domain.

npm run generate-project -- library data-access --scope=seatmap --grouping-folder=shared/seatmap --npm-scope=nrwl-airlines --with-state
# or
yarn generate-project library data-access --scope=seatmap --grouping-folder=shared/seatmap --npm-scope=nrwl-airlines --with-state
<>

Generate the seatmap data access library.

For now, we’ll put the feature store and effects in place by using the --with-stateparameter of the generate project tool. Note that we use the nested grouping folder shared/seatmap.

// seatmap-data-access.module.ts
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';

import { SeatmapEffects } from './+state/seatmap.effects';
import * as fromSeatmap from './+state/seatmap.reducer';

@NgModule({
  imports: [
    StoreModule.forFeature(fromSeatmap.seatmapFeatureKey, fromSeatmap.reducer),
    EffectsModule.forFeature([SeatmapEffects]),
  ],
})
export class SeatmapDataAccessModule {}
<>

The seatmap data access module.

The seatmap data access Angular module gives us an overview of what’s configured in the seatmap data access library. This is a good starting point.

ng run seatmap-data-access:lint

ng run seatmap-data-access:test --watch=false
<>

Lint and test the seatmap data access library.

Everything looks ready to go!

Seat listing feature library#

It’s time to add the first feature of the seatmap domain which is used in both the check-in and booking applications.

npm run generate-project -- library feature feature-seat-listing --scope=seatmap --grouping-folder=shared/seatmap --npm-scope=nrwl-airlines
# or
yarn generate-project library feature feature-seat-listing --scope=seatmap --grouping-folder=shared/seatmap --npm-scope=nrwl-airlines
<>

Generate the seatmap seat listing feature library.

Our tool generates an Angular module and a component for us.

To add this feature to our applications, we add a route to each feature shell module.

// check-in-feature-shell.module.ts
import { CommonModule } from '@angular/common';
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: [
      {
        path: '',
        pathMatch: 'full',
        redirectTo: 'seatmap', // 👈
      },
      {
        path: 'seatmap', // 👈
        loadChildren: () =>
          import('@nrwl-airlines/seatmap/feature-seat-listing')
            .then(esModule => esModule.SeatmapFeatureSeatListingModule),
      },
    ],
  },
];

@NgModule({
  declarations: [ShellComponent],
  exports: [RouterModule],
  imports: [
    RouterModule.forRoot(routes),
    SharedDataAccessModule,
    CheckInDataAccessModule,
    CommonModule,
  ],
})
export class CheckInFeatureShellModule {}

#angular #angular-cli #angular-workspace #monorepo #nx #series-angular-architectural-patterns #ngrx

Roberta  Ward

Roberta Ward

1595337024

Tutorial: Nx-style monorepo workspace with Angular CLI: Part 3

In Part 3 of this tutorial, we create the passenger info and flight search feature libraries. We use the generate project tool to create the mobile booking application and its test project. Finally, we create a mobile version of the flight search component template.

This tutorial is part of the Angular Architectural Patterns series.

In Part 2 of this tutorial, we used the generate project tool to generate the booking data access and shared data access workspace libraries with NgRx Store and Effects. We extracted a shared environments library and hooked everything up to the booking feature shell library.

In this part of the tutorial, we’re going to create the passenger info and flight search feature libraries, each with a routed component. After that, we’ll create the mobile booking application project and its end-to-end test project. Finally, we’ll use builder file replacement to create a mobile version of the flight search component template.

THIS AD MAKES CONTENT FREE. HIDE

Passenger info feature library#

Let’s create our first feature library, the passenger info feature which is part of the booking domain.

npm run generate-project -- library feature feature-passenger-info --scope=booking --npm-scope=nrwl-airlines
# or
yarn generate-project library feature feature-passenger-info --scope=booking --npm-scope=nrwl-airlines
<>

Generate passenger info feature library.

After generating the project using the previous commands and parameters, we get this file and folder structure.

libs/booking/feature-passenger-info
├── src
│   ├── lib
│   │   ├── passenger-info
│   │   │   ├── passenger-info.component.css
│   │   │   ├── passenger-info.component.html
│   │   │   ├── passenger-info.component.spec.ts
│   │   │   └── passenger-info.component.ts
│   │   ├── booking-feature-passenger-info.module.spec.ts
│   │   └── booking-feature-passenger-info.module.ts
│   ├── index.ts
│   └── test.ts
├── README.md
├── karma.conf.js
├── tsconfig.lib.json
├── tsconfig.spec.json
└── tslint.json
<>

The file and folder structure of the booking passenger info feature library.

This looks a little different from a feature shell library and a data access library.

After the generate project tool has created the workspace library with an entry point Angular module, it runs the commands in the next listing.

The generate project tool also removed the --no-common-module flag from the ng generate module command we saw earlier, since this Angular module will be declaring components.

ng generate component passenger-info --project=booking-feature-passenger-info --module=booking-feature-passenger-info.module.ts --display-block
<>

Generate component command run when generating a feature library.

Let’s look at the Angular module our tool has generated.

// booking-feature-passenger-info.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';

import {
  PassengerInfoComponent,
} from './passenger-info/passenger-info.component';

@NgModule({
  declarations: [PassengerInfoComponent],
  imports: [
    CommonModule,
  ],
})
export class BookingFeaturePassengerInfoModule {}
<>

Initial entry point Angular module in the passenger info feature library.

The entry point Angular module shown in the previous listing is a good starting point. We need to set up the feature routing for our component though. This is done in the next listing.

// booking-feature-passenger-info.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import {
  PassengerInfoComponent,
} from './passenger-info/passenger-info.component';

const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    component: PassengerInfoComponent,
  },
];

@NgModule({
  declarations: [PassengerInfoComponent],
  imports: [
    RouterModule.forChild(routes),
    CommonModule,
  ],
})
export class BookingFeaturePassengerInfoModule {}
<>

Passenger info feature Angular module with route configuration for its entry point component.

Nice! Now we’ve prepared our feature library to be hooked up to the feature shell library’s routing configuration.

The generated component is what you’d expect. What it’d display in a real booking application is not really important for the purpose of this article.

Let’s hook up this feature to the booking application’s routing by adding a route configuration to the booking feature shell Angular module as seen here.

#angular #angular-cli #angular-workspace #monorepo #nx #series-angular-architectural-patterns

Roberta  Ward

Roberta Ward

1593061105

Tutorial: Nx-style monorepo workspace with Angular CLI: Part 2

In Part 1 of this tutorial, we set up the booking desktop application project, a project for its end-to-end test suite, and the booking feature shell workspace library.

In this part, we’ll set up our custom generate project tool to automate the steps we did manually in Part 1. We’ll use it to create the shared and booking data acess libraries with NgRx Store, NgRx Effects, NgRx Schematics, and NgRx Store DevTools.

To configure the data access libraries while keeping the flow of dependencies correct, we’ll extract a shared environments library. Data access will be hooked up to the booking feature shell library.

#angular #angular-cli #angular-workspace #monorepo #nx #series-angular-architectural-patterns #ngrx