The Angular Developers NX Handbook
The Angular Developers NX Handbook
The Angular Developers NX Handbook
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Creating an Nx workspace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Using the create-nx-workspace command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Creating an Angular CLI workspace for comparison . . . . . . . . . . . . . . . . . . . . . . . 3
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1 # npm
2 npm init nx-workspace <workpace-directory-name> --package-manager=npm
3
4 # Yarn
5 yarn create nx-workspace <workpace-directory-name> --package-manager=yarn
6
7 # pnpm
8 pnpm init nx-workspace <workpace-directory-name> --package-manager=pnpm
We use the following command to create an Nx workspace using Yarn as our package manager:
In the chapter Exploring a fresh Nx Angular workspace, we discuss the differences between Nx
Angular and Angular CLI workspaces.
Conclusion
You now have an Nx workspace in a directory named nx-angular-gitropolis which we generated
using the create-nx-workspace command. Which package manager did you choose? In this book, I
demonstrate how to use Yarn Classic–that is version 1.x–but feel free to pick your preference and
figure out any differences in terminal commands on your own.
We used the Angular workspace preset by passing the argument angular to the --preset parameter.
In the following chapter, we explore all the generated directories and files, then we discuss how they
differ from a workspace generated by the Angular CLI.
We specified other workspace options using the parameters --app-name, --npm-scope, and --style.
We also opted out of Nx Cloud by specifying the --no-nx-cloud parameter.
Note: If you want the full Nx experience, you should use the Nx Cloud. It comes with 500
free hours of compute time per month. While Nx Cloud is a significant benefit of using
Nx, it is not covered in this book.
Finally, you had the choice to generate a workspace using the Angular CLI to compare the
workspaces side-by-side. Don’t worry, you can proceed to the next chapter without generating an
Angular CLI workspace but it’s interesting to inspect and compare on your own while following
along.
Exploring a fresh Nx Angular
workspace
In this chapter, we explore the generated files and directories in our Nx workspace on a high level.
First, we compare top-level files, including configurations for:
• EditorConfig
• Git
• TypeScript
• Prettier
• ESLint
• Jest
• Nx
To improve our productivity and keep a consistent style, we configure code generation defaults in
the Nx CLI configuration.
Next, we explore top-level directories in our Nx workspace and compare them to what we know
from an Angular CLI workspace. This includes locations for:
We also keep a consistent style by configuring automatic organization of import statements when
source code is saved.
Finally, we introduce solution-style TypeScript configurations and their benefits since this configu-
ration style is used by the Nx CLI but not the Angular CLI.
Exploring a fresh Nx Angular workspace 5
In comparison, a freshly generated Angular CLI workspace looks like the following image:
Familiar files
Considering the root workspace files first, we see that they have the following files in common:
The name and content of the lockfile (yarn.lock) vary depending on the package manager we are
using.
TypeScript configurations
The Nx Angular workspace has a tsconfig.base.json file while the Angular CLI workspace has a
tsconfig.json file. As you might know, they are configuration files for the TypeScript compiler.
Compared to the Angular CLI’s TypeScript compiler configuration, Nx’s configuration has fewer
TypeScript compiler settings and at first glance does not have settings for the Angular compiler,
namely the well-known angularCompilerOptions object.
The Angular-specific settings, as well as strict settings, are not added to the base TypeScript
configuration by Nx’s Angular workspace preset. As it turns out, they are added to project-specific
TypeScript configurations for applications and libraries.
Nx uses solution-style TypeScript configurations which were introduced to Angular CLI 10.0 with
TypeScript 3.9 but rolled back in Angular CLI 10.1. Learn more about this TypeScript configuration
style in the section Introducing solution-style TypeScript configurations in this chapter.
Additional files
Most of the other top-level files are related to tools that Nx adds by default:
Prettier
Prettier is an open-source tool that normalizes file formatting. Prettier formats files in our workspace
based on settings in the .editorconfig and .prettierrc configuration files. In Nx workspaces, it is
used through the nx format commands. Read the chapter Increasing productivity with the Nx CLI’s
unique commands to learn how to use these commands.
Exploring a fresh Nx Angular workspace 7
ESLint
The open-source ESLint plugin Angular ESLint adds Angular-specific lint rules to ESLint. Nx’s Angu-
lar workspace preset preinstalls ESLint, TypeScript ESLint, and Angular ESLint with recommended
plugins and linting presets.
Note: As you might know, Angular CLI doesn’t come with a linter preinstalled since
support for the end-of-life tool TSLint was fully removed. However, if we try to use the
ng lint command in an Angular CLI workspace, we are prompted to install Angular
ESLint.
Jest
While Nx has built-in support for using Google’s Karma test runner and implicitly the Jasmine test
framework for unit tests, the open-source test runner and test framework Jest governed by The
OpenJS Foundation is the default choice for Nx projects.
ng command delegation
Our Nx workspace contains a file with the interesting name decorate-angular-cli.js. This file runs
as part of the postinstall hook listed in package.json. It delegates all supported ng Angular CLI
commands to the Nx CLI executable, nx. When your muscle memory makes you write ng generate
component and other common commands, the Nx CLI will be called instead of the Angular CLI.
Note: The decorate-angular-cli.js file is a Node.js script. You do not have to read or
understand it but it includes a description of its features with instructions for disabling
the patch entirely.
Workspace configuration
Before looking at nx.json, the final file added to the workspace root by Nx, let us open angular.json
in our Nx Angular workspace:
1 {
2 "version": 2,
3 "projects": {
4 "gitropolis-app": "apps/gitropolis-app",
5 "gitropolis-app-e2e": "apps/gitropolis-app-e2e"
6 }
7 }
Exploring a fresh Nx Angular workspace 8
Wow! Being familiar with the Angular CLI, I guess that’s not what you expected. Where are all the
project and CLI settings?
The "version": 2 setting signifies the workspace schema. The Angular CLI currently uses the
workspace version 1 schema, while the Nx CLI uses version 2. The supported settings are similar but
by default, Nx uses standalone project configurations. This means, that what we see in the projects
object is a collection of project names mapped to their paths in the workspace.
The project-specific settings are located in project.json files at the root of each project folder. For
example, the project.json file in the apps/gittropolis-app directory has content in the following
format which should mostly look familiar:
1 {
2 "projectType": "application",
3 "root": "apps/gitropolis-app",
4 "sourceRoot": "apps/gitropolis-app/src",
5 "prefix": "gitropolis",
6 "targets": {
7 "build": {
8 "//": "(...)"
9 },
10 "serve": {
11 "//": "(...)"
12 },
13 "extract-i18n": {
14 "//": "(...)"
15 },
16 "lint": {
17 "//": "(...)"
18 },
19 "test": {
20 "//": "(...)"
21 }
22 },
23 "tags": []
24 }
Later in this book, we discuss the differences between these settings and the settings we know from
an Angular CLI workspace configuration.
Nx CLI configuration
Now, you might be wondering: Where are the CLI settings? In Angular CLI workspaces, they are in
the angular.json file. In Nx workspaces, they are in nx.json:
Exploring a fresh Nx Angular workspace 9
1 {
2 "npmScope": "gitropolis",
3 "affected": {
4 "defaultBase": "main"
5 },
6 "cli": {
7 "defaultCollection": "@nrwl/angular",
8 "packageManager": "yarn"
9 },
10 "implicitDependencies": {
11 "//": "(...)"
12 },
13 "tasksRunnerOptions": {
14 "//": "(...)"
15 },
16 "targetDependencies": {
17 "//": "(...)"
18 },
19 "generators": {
20 "@nrwl/angular:application": {
21 "style": "css",
22 "linter": "eslint",
23 "unitTestRunner": "jest",
24 "e2eTestRunner": "cypress"
25 },
26 "@nrwl/angular:library": {
27 "linter": "eslint",
28 "unitTestRunner": "jest"
29 },
30 "@nrwl/angular:component": {
31 "style": "css"
32 }
33 }
34 }
The cli settings might look familiar. The generators object we know as schematics in Angular CLI
workspace configurations. Notice that Nx comes with its own set of Angular generators which is
what schematics are called in Nx workspaces. The other settings are specific to Nx. We discuss them
later in this book.
In this workspace, we create single-file standalone Angular components that use the OnPush change
detection strategy. Let us configure our generator defaults so that we only have to specify parameters
that are unique to a component when generating them.
Exploring a fresh Nx Angular workspace 10
As this workspace only has a single application project, let us make it the default project for Nx CLI
commands by adding the following setting to the nx.json workspace configuration file:
1 {
2 "defaultProject": "gitropolis-app"
3 }
To prefer Nx Angular generators over the Angular CLI’s generators, we configure the
cli.defaultCollection setting in nx.json:
1 {
2 "cli": {
3 "defaultCollection": "@nrwl/angular"
4 }
5 }
1 {
2 "generators": {
3 "@nrwl/angular:component": {
4 "style": "scss",
5 "changeDetection": "OnPush",
6 "displayBlock": true,
7 "flat": true,
8 "inlineStyle": true,
9 "inlineTemplate": true,
10 "skipTests": true,
11 "standalone": true
12 }
13 }
14 }
In the chapter Creating an Nx workspace, we specified the --style=scss argument when creating
the workspace so the "style": "scss" setting should already be present.
Now, let us also configure code generation defaults for generating standalone Angular directives
and pipes by adding the following settings to the nx.json file:
Exploring a fresh Nx Angular workspace 11
1 {
2 "generators": {
3 "@schematics/angular:directive": {
4 "skipTests": true,
5 "standalone": true
6 },
7 "@schematics/angular:pipe": {
8 "skipTests": true,
9 "standalone": true
10 }
11 }
12 }
In addition to making standalone Angular declarables, we skip test generation since we will only be
covering a few classes and features with tests throughout this book.
We also skip test generation for services by adding these settings:
1 {
2 "generators": {
3 "@schematics/angular:service": {
4 "skipTests": true
5 }
6 }
7 }
Finally, we configure similar settings for application projects for the sake of consistency:
1 {
2 "generators": {
3 "@nrwl/angular:application": {
4 "style": "scss",
5 "inlineStyle": true,
6 "inlineTemplate": true,
7 "skipTests": true
8 }
9 }
10 }
Note: In addition to Nx Console, make sure to install the recommended Prettier and
ESLint extensions for VS Code to get the best developer experience.
To configure VS Code to prefer the TypeScript version installed in the workspace, we create a
settings.json file in the .vscode directory with the following contents:
1 {
2 "typescript.tsdk": "./node_modules/typescript/lib"
3 }
This increases the accuracy of TypeScript suggestions and syntax error detection when editing source
code.
Additionally, we add the following settings to the setting.json file to organize imports on save:
1 {
2 "editor.formatOnSave": true,
3 "[typescript]": {
4 "editor.codeActionsOnSave": {
5 "source.organizeImports": true
6 }
7 }
8 }
This increases consistency in our codebase and decreases the number of changes and merge conflicts
because of different editor settings across developer machines. ESLint plugins to manage this are
available but more complicated to configure so we configure VS Code in this book instead.
Library projects
By now, you must be wondering what is in the libs directory, that is if you have not yet peeked
and found it to be empty. This is where we generate library projects which contain most of our
code. Unlike libraries generated by the Angular CLI, these libraries are configured by default to be
directly referenced from application projects through TypeScript path mappings rather than having
to be built and output to the dist directory before usage.
Note: Nx library projects do not have to be packaged, published, reused, or shared. Making
this assumption is a common mistake when learning Nx since the Angular CLI only
has native support for generating publishable library projects to distribute in package
registries such as npm.
Note: Even Protractor is still supported despite it being unmaintained and removed from
official Angular CLI generators and executors. Enjoy it while it lasts! Or not.
In our case, the Cypress project is generated in the gitropolis-app-e2e directory next to the
gitropolis-app application project directory. This is different from when previous versions of the
Angular CLI generated end-to-end tests inside the application project directory that they covered.
One of the benefits of this setup is that our editor will understand that Node.js and testing APIs are
unavailable in application source files. Notice, however, that in the end-to-end testing project, only
a single TypeScript configuration file is generated, namely tsconfig.json. All files in end-to-end
testing projects share the same compilation settings since only test suites and their supporting files
are included, with no application source code.
Because Nx supports a range of technologies other than Angular, Angular compilation settings are
added to each Angular-specific project rather than to tsconfig.base.json.
Conclusion
In a nutshell, when comparing a fresh workspace generated by the Angular CLI, the following list
describes a fresh workspace generated by Nx’s Angular workspace preset:
• The root workspace directory contains shared or base configurations for EditorConfig, ESLint,
Git, Prettier, Jest, the Nx CLI, and the TypeScript compiler
• Instead of a single projects directory, our projects are split into the apps and libs directories
• End-to-end testing projects are generated next to the applications they cover, in the apps
directory
• Every project has a standalone project.json configuration instead of it being part of
angular.json
• The tools directory contains local executors and generators but is also a good location for
scripts used to support our workspace
• Solution-style TypeScript configurations are used in application projects
To keep a consistent style of creating single-file standalone Angular components, directives, and
pipes, we configured code generation defaults by adding settings to the nx.json file. Additionally,
we configured VS Code to prefer the TypeScript version installed in the workspace and to organize
imports on file save.
Do you think Nx uses solution-style TypeScript configurations for library projects? The answer is
revealed later in this book.
Gear up for the next chapter where we learn Nx commands that are familiar, coming from the
Angular CLI.
Feeling right at home with familiar
Nx CLI commands
We all know and love the Angular CLI for all of its useful commands. With a few exceptions and
differences, the Nx CLI supports the same commands and then some. In this chapter, we compare
Angular CLI commands to Nx CLI commands.
We get familiar with the following Nx CLI commands, most of which sound familiar to Angular
developers like us:
• create-nx-workspace
• nx build
• nx serve
• nx generate
• nx lint
• nx test
• nx e2e
• nx run
• nx deploy
• nx extract-i18n
• nx migrate
• nx report
• nx analytics
• nx doc
• nx --help
The Nx CLI comes with a few more important commands which are covered in later chapters.
The create-nx-workspace executable has other built-in presets and even supports third-party and
custom presets, none of which are covered in this book though.
The --npm-scope parameter indicates the import alias used to reference projects in the same
workspace or for publishable libraries, that is packages distributed through package registries such
as npm. An example is --npm-scope=gitropolis in which case we could generate and reference a
library such as @gitropolis/repositories/data-access-github.
The --app-name parameter specifies the project name of the generated application, for example
--app-name=gitropolis-app (the app suffix is optional). An end-to-end testing project is generated
using the same name concatenated with an -e2e suffix, for example gitropolis-app-e2e. Both of
these projects are generated in the apps directory.
The --package-manager parameter supports the values npm, yarn, and pnpm. I recommend against
using pnpm as package manager as long as your workspace or its dependencies rely on the Angular
Compatibility Compiler (ngcc) as this compiler does not support modern module layouts such as
those used by npm (7+) Arborist, Yarn (2+) Plug’n’Play, or the pnpm virtual store.
If you use a legacy default branch name, that is a branch name other than main, make sure to pass
it to the --default-base parameter, for example --default-base=master.
As seen in the previous example, nx build is a shorthand to run the build target of the specified
project, same as ng build.
1 nx serve gitropolis-app
2 # is the same as
3 nx run gitropolis-app:serve
Add the --hmr parameter to enable Webpack Hot Module Replacement (HMR), for example:
Try out the nx serve command with the --open parameter to see the Gitropolis application running:
This opens your browser with the address https://localhost:4200 and you should see the Nx
welcome component:
Feeling right at home with familiar Nx CLI commands 18
The nx generate command accepts the name and optionally the namespace of a generator or
schematic followed by generator-specific parameters and their values.
As an example, the following commands generate an Angular application project named
gitropolis-app:
Because the value of the cli.defaultCollection setting in nx.json is @nrwl/angular, the gen-
erators that come with Nx’ Angular preset are the default rather than those available in the
@schematics/angular package.
The following common Nx Angular generators are available from the @nrwl/angular package:
• application
• library
• component
• scam
• scam-directive
• scam-pipe
• move
The move schematic is used to rename an Angular application or library project and/or to move it to
a different directory.
The @nrwl/workspace package has the commonly used remove Nx generator which deletes a project
and all references to it in shared workspace configuration files.
More generators are available, use the nx list command to list them all:
Note: The interactive Nx Console extension for VS Code is the best tool for exploring the
generators and targets that are available in your Nx Angular workspace.
Nx’ application and library generators for Angular projects have many options that are not
included with the Angular CLI’s application and library generators. We explore these in later
chapters of this book.
Feeling right at home with familiar Nx CLI commands 20
You might not know this but Angular CLI looks for an ng-add schematic in a package when the ng
add command is invoked. Unfortunately, we have to do this manually when using Nx. Additionally,
the default name of an equivalent Nx generator is init, not ng-add. For example, to add support for
Storybook to your Nx Angular workspace, use the following commands:
1 nx lint gitropolis-app
2 # is the same as
3 nx run gitropolis-app:lint
An Nx Angular workspace is generated with ESLint and Angular ESLint configured with Nrwl’s
recommended lint presets and Nx-specific lint rules.
Add the --fix parameter to run lint fixers, for example:
Feeling right at home with familiar Nx CLI commands 21
1 nx test gitropolis-app
2 # is the same as
3 nx run gitropolis-app:test
An Nx workspace is generated with Jest configured for unit testing applications and library projects.
While not the default option, Karma also has first-class support.
Adding the --watch parameter, starts Jest or Karma in file watch mode rather than exiting after
running all of a project’s test suites, for example:
If this is your first time using Jest, press the A key to watch all test suites in the project or explore the
other options listed in the interactive prompt. Alternatively, add the --watch-all parameter when
using Jest, for example:
When using Jest, adding the --code-coverage parameter, outputs an HTML test coverage report
to a sub-directory of the coverage directory in the workspace root, for example run the following
command:
The previous command outputs a test coverage report to the following directory:
1 coverage/apps/gitropolis-app
Other --coverage-reporters values include html (the default), json, lcov, and cobertura.
Similarly, third-party test execution reporters can be specified using the --reporters parameter, for
example:
1 nx e2e gitropolis-app-e2e
2 # is the same as
3 nx run gitropolis-app-e2e:e2e
An Nx workspace is generated with Cypress configured for end-to-end testing applications and
library projects. While not the default option, Protractor also has first-class support.
Adding the --watch parameter, starts Cypress or Protractor in file watch mode rather than exiting
after running all of an application’s test suites, for example:
If this is your first time using Cypress, file watch mode will open the Cypress application. Click
the filename of the test suite you want to run or click the label saying Run <x> integration specs to
the right of the collapsible section saying INTEGRATION TESTS. The tests will be rerun when you
change and save a test suite or cause the application to be rebuilt.
Running the e2e target of a Cypress project runs all of its test suites once in the Cypress application,
then closes the Cypress application and reports the result in the console output. For CI environments
such as hosted GitHub Actions runners, set the headed parameter to false, for example:
This will run all tests once in a headless browser without opening the Cypress application.
Test execution reporters for the Mocha testing framework can be specified using the --reporter
parameter, for example:
Feeling right at home with familiar Nx CLI commands 23
This command generates a test-results.xml test report in the end-to-end testing project directory.
Mocha has many built-in test reporters and Cypress includes the teamcity and junit Mocha
reporters, all of which are available out-of-the-box when using Cypress.
1 nx run gitropolis-app:build:production
Alternatively, the name of a configuration can be passed to the --configuration parameter, for
example:
If no configuration argument is specified, the default configuration will be used if one is defined in
the project configuration.
Note: A deploy target is not automatically generated but if we run the nx deploy
command, the Angular CLI prompts us to install one of several common deployment
packages for Angular projects.
If we are using Angular’s built-in internationalization APIs, this is an important command. If not,
feel free to delete the extract-i18n target in an Angular application’s project.json file.
Note: It is possible to migrate using the Angular CLI by setting the environment variable
FORCE_NG_UPDATE to true, then running the nx update command, for example:
1 nx update @angular/material
The nx migrate is also used for updating dependencies and running migrations but is used in a
slightly different way. If no package is specified, the nx migrate command assumes that we mean
the @nrwl/workspace package, for example:
1 nx migrate latest
2 # is the same as
3 nx migrate @nrwl/workspace@latest
This updates all packages that are managed by Nx, for example:
• @nrwl packages
• @angular packages
• ESLint, Angular ESLint, TypeScript ESLint, and other ESLint plugins managed by Nx
• Jest
• Cypress
• TypeScript
• Prettier
After running the nx migrate command, a migrations.json file is generated which contains a list
of available migrations. The next step is to update the affected packages to compatible versions, for
example:
Feeling right at home with familiar Nx CLI commands 25
1 yarn install
After installing the packages, we review the migrations.json file and remove any migrations that
we don’t want to run before passing the --run-migrations parameter:
1 nx migrate --run-migrations
If we want to run migrations in multiple batches, we can split them into separate files, run them one
at a time, delete the migration file, then committing the changes, for example:
1 nx migrate --run-migrations=migrations-nx.json
2 rm migrations-nx.json
3 git add .
4 git commit --message "chore: migrate Nx packages"
5 nx migrate --run-migrations=migrations-angular.json
6 git add .
7 git commit --message "chore: migrate Angular packages"
The nx migrate command supports the --from parameter to set a range of migrations to run for
specific packages, for example:
to migrate to the latest Nx 14 version while running all Nx workspace and Nx Angular migrations
since version 13.0.0, ignoring the locally installed versions of the specified packages.
Similarly, the --to parameter is supported, for example:
to migrate to the latest Nx packages while staying on version 14.0.0 of the @nrwl/angular package,
preventing migrations for versions newer than that from running.
1 nx report
2
3 > [NX] Report complete - copy this into the issue template
4 Node : 16.15.1
5 OS : win32 x64
6 yarn : 1.22.19
7
8 nx : 14.2.4
9 @nrwl/angular : 14.2.4
10 @nrwl/cypress : 14.2.4
11 @nrwl/detox : Not Found
12 @nrwl/devkit : 14.2.4
13 @nrwl/eslint-plugin-nx : 14.2.4
14 @nrwl/express : Not Found
15 @nrwl/jest : 14.2.4
16 @nrwl/js : Not Found
17 @nrwl/linter : 14.2.4
18 @nrwl/nest : Not Found
19 @nrwl/next : Not Found
20 @nrwl/node : Not Found
21 @nrwl/nx-cloud : Not Found
22 @nrwl/nx-plugin : Not Found
23 @nrwl/react : Not Found
24 @nrwl/react-native : Not Found
25 @nrwl/schematics : Not Found
26 @nrwl/storybook : 14.2.4
27 @nrwl/web : Not Found
28 @nrwl/workspace : 14.2.4
29 typescript : 4.7.3
30 ---------------------------------------
31 Community plugins:
32 nx-stylelint: 13.4.0
When submitting a bug report to the nrwl/nx GitHub repository, make sure to generate and include
an Nx report such as the sample above. When asking for support in GitHub Discussions for the
nrwl/nx GitHub repository or in the @nrwl/community Slack workspace, also generate and include
an Nx report. This makes it easier for Nrwlians and Nx community members to help you out.
Note: Unlike ng version, the nx report command also lists third-party packages that are
registered as Nx community plugins.
Another use case for the nx report command is to verify our assumptions about a local or remote
environment, for example a colleague’s local development environment or a GitHub-hosted runner
Feeling right at home with familiar Nx CLI commands 27
To stop sending Angular CLI usage metrics to Google, use the disable or off setting:
To read our current settings for Angular CLI usage metrics, we pass the info value:
For an interactive prompt to configure settings for Angular CLI usage metrics, pass the prompt value:
1 # Open interactive prompt to configure settings for sending Angular CLI metrics to G\
2 oogle
3 nx analytics prompt
1 nx run --help
Adding the --help parameter to an Nx command outputs supported parameters and options for the
specified Nx command.
Conclusion
In this chapter we learned about the Nx CLI commands that correspond to Angular CLI commands,
namely these familiar commands:
Instead of the ng deploy and ng extract-i18n commands, we use the nx run command to execute
these targets.
We also learned about Nx alternatives to certain Angular CLI commands:
Feeling right at home with familiar Nx CLI commands 29
The following Angular CLI commands are incompatible with the Nx CLI as of Nx version 14.2:
• ng cache: Unfortunately, there is no way access the Angular CLI’s cache command when using
the Nx CLI. However, we can configure the Angular CLI’s cache settings through the cli.cache
property of nx.json, for example:
1 {
2 "cli": {
3 "cache": {
4 "environment": "local",
5 "path": ".angular/cache"
6 }
7 }
8 }
The Angular CLI’s cache is output to the .angular/cache directory by default. Since we cannot
clean it through the Angular CLI’s cache command, we delete it manually instead.
• ng completion: There are no terminal completions for Nx as of Nx version 14.2. Instead, it is
recommended to use the interactive Nx Console extension for VS Code.
• ng config: To configure Angular CLI-specific settings, add them to the cli property in the
nx.json file as described for the Angular cache settings above.
In this chapter, we have learned that–with a few exceptions–the Nx CLI is familiar to Angular
developers. When we compare Nx CLI commands to their Angular CLI equivalents, we know about
these improvements:
• More built-in generators, for example the move and remove generators
• nx lint comes with preconfigured linters and lint rules
• nx e2e comes with Cypress end-to-end tests
• A more customizable migration experience with the nx migrate command when compared to
ng update
Generators have more options than their Angular CLI equivalents, especially the library generator
which we will learn about in upcoming chapters.
Some of the primary benefits of using Nx are the Nx CLI commands that have no equivalents in the
Angular CLI. We learn about the format, list, run-many, affected, and graph commands in later
chapters.
Cleaning up the application project
In this chapter, we clean up the Gitropolis application project to prepare our workspace for
implementing our first application features.
We learn how to:
The classes on the <div> element are part of Primer which we will install in Steps 8-9.
5. Delete the app.module.ts file.
6. Change the contents of the main.ts file in the apps/gitropolis-app/src directory to match
the following:
7. Create an app.routes.ts file with the following contents, adding no top-level routes for now:
Cleaning up the application project 32
1 @use '@primer/css/index';
Make sure that the application works by running the nx serve --open command. You should see
the text Gitropolis and there should be no errors in your browser console.
Conclusion
In this short chapter, we cleaned up our application by deleting the Nx welcome component,
converting the Gitropolis application to a standalone Angular application currently consisting of
a standalone root component with the text Gitropolis and a router outlet. We modified the main.ts
file to bootstrap our standalone application and created the app.routes.ts file where we place top-
level routes in later chapters.
Finally, we set up GitHub’s Primer design system so that Gitropolis has a style similar to GitHub.
Before we start implementing the first Gitropolis application feature, we are going to learn important
Nx library concepts. The next chapter covers Nx library traits and their characteristics.
¹https://primer.style
Choosing library traits
Before we create our first Nx Angular library, we should know about library traits and library types.
In this chapter covering library traits, we:
• Workspace library
• Buildable library
• Publishable library
Note: Nx has generators for non-Angular-specific libraries whose traits share most of
the characteristics explained in this chapter, for example the @nrwl/workspace:library
generator.
As illustrated in the following diagram, a publishable library is a buildable library and a buildable
library is a workspace library:
Nx library traits
Choosing library traits 34
Workspace libraries
A workspace library:
This supports running lint checks and unit tests for a single library instead of the whole workspace.
It also enables us to import this library into other workspace projects to reference and use its publicly
available components, services, and other software artifacts.
Workspace libraries are built as part of one or more applications.
Note: Nx has a lint rule which prevents other projects from making deep imports, that is
importing software artifacts that are not exposed through a library’s entry point(s).
For demonstration purposes, we generate a library called workspace-library using the following
command:
The name does not have to be workspace-library. The name of a library should not include its
library trait(s).
The following file tree shows the generated files for this Nx Angular workspace library:
Choosing library traits 35
The following files are generated in the root directory of a workspace library:
23 },
24 "tags": []
25 }
• README.md: A project readme file listing the Nx command to run the project’s unit test suites.
• tsconfig.json: The base TypeScript configuration of the library project including Angular
compilation settings. Extends tsconfig.base.json from the workspace root directory and
references the other TypeScript configuration files in this project.
• tsconfig.lib.json: Configures compilation of library source files.
• tsconfig.spec.json: Configures compilation of test suites running Jest in Node.js.
Next, we have the src (source) directory. It contains the following files:
• index.ts: The primary entry point. This barrel file is the public API of the library in which
exported software artifacts are exposed to other projects in the workspace.
• test-setup.ts: This test setup file is executed by Jest before any test suites are loaded. It sets
up jest-preset-angular.
Finally, the lib (library) directory inside the src directory is where we place the source code of our
library. To expose software artifacts to the rest of the workspace, we re-export them in the primary
entry point, that is index.ts.
When an Nx library is generated, a project entry is added to the angular.json workspace
configuration file, for example:
1 {
2 "projects": {
3 "buildable-library": "libs/buildable-library"
4 }
5 }
The key buildable-library is the Nx project name used for configurations and commands, for
example when running the nx build command:
1 nx build buildable-library
1 {
2 "compilerOptions": {
3 "paths": {
4 "@workspace/buildable-library": ["libs/buildable-library/src/index.ts"]
5 }
6 }
7 }
This enables our editor to recognize when the library is imported into other projects in the workspace.
In case of buildable libraries, without having built the library which is useful for development.
Buildable libraries
A buildable library:
Buildable libraries are built independently from other projects. They are used to support Nx’
incremental builds feature. The ng-packagr-lite executor only produces a bundle in the latest
ECMAScript Module (ES Module) version, for example the esm2020 format.
Note: Applications that depend on buildable libraries should use an executor that supports
incremental serve for its serve target, for example the @nrwl/web:file-server executor.
For demonstration purposes, we generate a library called buildable-library using the following
command:
The name does not have to be buildable-library. The name of a library should not include its
library trait(s).
The following file tree shows the generated files for this buildable Nx Angular library:
Choosing library traits 38
In addition to the files generated for a workspace library, the following files are generated in the
root directory of a buildable library:
In addition to lint and test targets, the project.json project configuration file declares a build
target, for example:
1 {
2 "targets": {
3 "build": {
4 "executor": "@nrwl/angular:ng-packagr-lite",
5 "outputs": ["dist/libs/buildable-library"],
6 "options": {
7 "project": "libs/buildable-library/ng-package.json"
8 },
9 "configurations": {
10 "production": {
Choosing library traits 39
11 "tsConfig": "libs/buildable-library/tsconfig.lib.prod.json"
12 },
13 "development": {
14 "tsConfig": "libs/buildable-library/tsconfig.lib.json"
15 }
16 },
17 "defaultConfiguration": "production"
18 }
19 }
20 }
Publishable libraries
A publishable library:
Note: When we do not have a specific reason to pick other library traits, we generate
workspace libraries.
For demonstration purposes, we generate a library called publishable-library using the following
command:
The name does not have to be publishable-library. The name of a library should not include its
library trait(s).
Note: We must manually specify the import path of a publishable library, for example
@gitropolis/publishable-library.
Choosing library traits 40
The following file tree shows the generated files for this publishable Nx Angular library:
The number and type of files generated in the root directory of a publishable library are the same
as for a buildable library. The differences lie in 2 files:
• project.json: The build target uses a fully Angular Package Format (APM)-compatible
executor, for example:
1 {
2 "targets": {
3 "build": {
4 "executor": "@nrwl/angular:package",
5 "outputs": ["dist/libs/publishable-library"],
6 "options": {
7 "project": "libs/publishable-library/ng-package.json"
8 },
9 "configurations": {
10 "production": {
11 "tsConfig": "libs/publishable-library/tsconfig.lib.prod.json"
12 },
13 "development": {
14 "tsConfig": "libs/publishable-library/tsconfig.lib.json"
15 }
16 },
17 "defaultConfiguration": "production"
18 }
19 }
20 }
• tsconfig.lib.prod.json: The partial Angular Ivy compilation mode is used, that is:
Choosing library traits 41
1 {
2 "angularCompilerOptions": {
3 "compilationMode": "partial"
4 }
5 }
This adds a secondary entry point to the publishable-library library with the sub-package name
testing.
The following file tree shows a freshly generated secondary entry point named testing for a
publishable Nx Angular library:
A secondary entry point directory is created as a sibling directory to the primary entry point’s src
directory. In the previous screenshot, this directory is called testing because of the implicit name
argument we specified when running the nx generate command.
In the root secondary entry point directory, 2 files are generated:
1 {
2 "lib": {
3 "entryFile": "src/index.ts"
4 }
5 }
• README.md: A project readme file listing the import path for the secondary entry point.
Other than these files, a src directory is created, containing the secondary entry point barrel file,
that is index.ts, which exposes software artifacts to consumers of the package bundle, including
other projects in the workspace.
Finally, a lib directory is created in which we place library source code.
Note: A secondary entry point cannot have a separate package description, that is
a package.json file. Instead, consider making its unique peer dependencies optional
through configuration.
The secondary entry point belongs to the publishable library project so no additional entry is created
in the workspace configuration file. However, a path mapping is added to the base TypeScript
configuration, for example:
1 {
2 "compilerOptions": {
3 "paths": {
4 "@workspace/publishable-library": [
5 "libs/publishable-library/src/index.ts"
6 ],
7 "@workspace/publishable-library/testing": [
8 "libs/publishable-library/testing/src/index.ts"
9 ]
10 }
11 }
12 }
Conclusion
In this chapter, we learned about the characteristics of the 3 different Nx library traits, namely
workspace, buildable, and publishable libraries. As a rule of thumb, we generate workspace libraries
unless we have a reason to choose another library trait. If we want to use incremental build and
incremental serve, both features offered by Nx, we generate buildable libraries. However, these build
optimization features require more than just buildable libraries and are not covered by this book. To
Choosing library traits 43
create a package that we want to publish in a private or public package registry, we generate a
publishable library.
Workspace libraries have lint and test targets while buildable libraries add a build target that
uses the ng-packagr-lite executor which only outputs one bundle format in order to optimize build
performance. Publishable libraries use a fully Angular Package Format (APM)-compatible executor
for their build target.
These library management features are handled by Nx:
• When a library is built, peer dependencies declared in its package description are automatically
updated based on import statements in its source code.
• All libraries add at least one path mapping to the tsconfig.base.json base TypeScript
configuration file, allowing us to reference them without having built them. This is useful
for development.
• Buildable libraries support both the full and partial Angular Ivy compilation modes while
publishable libraries must be built using the partial Angular Ivy compilation mode.
• Publishable libraries support secondary entry points while workspace libraries and buildable
libraries only support a primary entry point.
Note: If you are familiar with developing .NET systems, think of an Nx workspace as
a .NET solution. Similarly, we can think of applications and libraries as .NET projects.
They might be published packages but they might also be logical groupings in our system,
referenced by other projects in the solution.
When creating a library intended to be used by applications, we must carefully pick a library type
depending on the concerns we intend it to address. There are 4 conventional Nx library types:
While an Nx library can only have one library type, we can define as many or as few library types
as we want in our workspace. For example, this chapter introduces the following library types for
testing purposes:
Note: Feel free to redefine the library types introduced in this chapter in the context of
your workspace, including the conventional Nx library types.
In particular, we discuss which application concerns a library types addresses, which software
artifact types it contains, the library types it is allowed to depend on, and naming conventions.
Application concerns
Angular applications address many concerns:
• Business logic, for example application-specific logic, domain logic, and validation rules
• Persistence, for example WebStorage, IndexedDB, WebSQL, HTTP, WebSocket, GraphQL,
Firebase, and Meteor
• Messaging, for example WebRTC, WebSocket, Push API, and Server-Sent Events
• I/O, for example Web Bluetooth, WebUSB, NFC, camera, microphone, proximity sensor, and
ambient light sensor
• Presentation, for example DOM manipulation, event listeners, and formatting
• User interaction, for example UI behavior and form validation
• State management, for example application state management and application-specific events
• Persistence
• Messaging
• I/O
• State management, that is server state, persistent state, client state, transient client state, URL
state, and router state
• Data services
• HTTP interceptors
• Global or shared state management
• Utility libraries
• Test utility libraries
The name of a data access library is prefixed with data-access-, for example data-access-github,
or the full name can be data-access when the library is located in a grouping directory, for example
libs/repositories/data-access.
UI libraries
A UI library is either a collection of reusable presentational declarables and services, or it is a
collection of application-, feature- or domain-specific presentational declarables and services.
• Presentation
• User interaction
• State management, that is local UI state
• Presentational components
• Directives
• Pipes
• Presentational services, for example presenters
• UI libraries
• Utility libraries
• Test utility libraries
The name of a UI library is prefixed with ui-, for example ui-design-system, or the full name can
be ui when the library is located in a grouping directory, for example libs/repositories/ui.
Feature libraries
A feature library can represent a page, a screen, a use case, or any other logical grouping. It
contains one or more components that may or may not be routed. A feature library orchestrates
data and interactions between our application and our user. Feature libraries are used in one or
more applications. The components contained by a feature library are included in an application
through elements in component templates, eagerly loaded routes, lazy-loaded routes, or dynamic
rendering.
We can choose to make a feature library address the same concerns as UI libraries or we can leave
those concerns to UI libraries. For example, we might have multiple container components in one
or more feature libraries that use a presentational component contained by a UI library.
Other than concerns shared with UI libraries, a feature library deals with the following concerns:
• Integrating presentational concerns and business logic with application state management
• Smart components, for example routed components, page components, container components,
and mixed components
• Routing, that is routes, route paths, route guards, route resolvers, route reuse strategies, and
preloading strategies
Picking a library type 48
The name of a feature library is prefixed with feat-, for example feat-repository-list, or
the full name can be feat when the library is located in a grouping directory, for example
libs/repositories/feat.
Utility libraries
A utility library is a collection of reusable logic and values.
A utility library can contain:
• Stateless services
• Pure functions
• Constants
• Utility types
• Utility libraries
• Test utility libraries
Picking a library type 49
The name of a utility library is prefixed with util-, for example util-date-times, or the
full name can be util when the library is located in a grouping directory, for example
libs/repositories/util.
The name of a test utility library is prefixed with test-util-, for example test-util-browser, or
the full name can be test-util when the library is located in a grouping directory, for example
libs/repositories/test-util.
End-to-end test utility libraries can depend on the following library types:
• Utility libraries
• Test utility libraries
• End-to-end test utility libraries
Picking a library type 51
The name of an end-to-end test utility library is prefixed with e2e-util-, for example
e2e-util-dom-matchers, or the full name can be e2e-util when the library is located in a
grouping directory, for example libs/shared/e2e-util.
Domain libraries
A domain library deals with the following concerns:
• Business logic
A domain library can contain the following software artifact types as long as they are related to a
specific business domain:
• Domain types
• Domain object classes
• Domain services
• Domain constants
• Domain functions
• Utility libraries
• Test utility libraries
• Domain libraries
Picking a library type 52
The name of a domain library is prefixed with domain-, for example domain-user, or the
full name can be domain when the library is located in a grouping directory, for example
libs/repositories/domain.
Conclusion
Application projects should almost exclusively depend on feature libraries. An application must not
depend directly on data access libraries, UI libraries, or domain libraries.
Feature libraries are a good starting point. We could choose to start by having all logic for an entire
page or screen in a feature library but as our implementation grows, we split it into multiple feature
libraries and/or start extracting concerns to more appropriate library types.
We defined these library types which we use throughout this book:
In the next chapter, we create our first libraries to make room for our first application feature.
Creating a feature library
It’s time to create our first Nx Angular library. In this chapter, we:
The name passed to the nx generate library command follows the format
<library-type>[-<library-name>], in this case feat-repositories where feat is shorthand
for feature library and repositories is the library name.
We choose to identify the domain by the top-level directory name by passing organizations to the
--directory parameter. The --prefix parameter specifies the default prefix for components and
other software artifacts generated in this library, in this case the prefix is gitropolis-orgs.
Delete the generated organizations-feature-repositories.module.ts file in the library’s src/lib
directory containing an Angular module.
A feature component is special because it will either be routed or included in the template
of a component in another feature library. It is the entry point of a feature library.
The --export parameter flag exports the feature component from our feature library’s primary entry
point, the barrel file, that is src/index.ts. This component is now part of our feature library’s public
API surface, allowing other projects to import it.
We pass the Nx project name of our new library to the --project parameter. Notice
how it follows the format <domain>-<library-type>-<library-name>, in this case
organizations-feat-repositories.
1 {
2 path: 'orgs/:organization/repositories',
3 loadComponent: () =>
4 import('@gitropolis/organizations/feat-repositories').then(
5 (m) => m.OrganizationsFeatRepositoriesComponent
6 ),
7 },
The route path has an organization route parameter. The lazy-loaded route resolves the feature
component.
Notice that we refer to our feature library using the import path alias @gitropolis/organizations/feat-repositori
which follows the format @<workspace-npm-scope>-<domain>-<library-type>-<library-name>.
The import path alias closely matches the library path which is libs/organizations/feat-repositories.
Let us try out the feature, shall we?
Run nx serve --open to start a development server and https://localhost:4200 is automatically
opened in your default browser when the server is ready. You should see the application title
Gitropolis.
Now, enter the following URL and press your Enter key:
1 http://localhost:4200/orgs/nrwl/repositories
The small text organizations-feat-repositories works! should be visible under the application title.
Congratulations, you created our first routed feature!
Creating a feature library 55
Note: With the Nx Console extension, we can right-click a directory and press the Nx
generate... menu item in the context menu to automatically specify values for the
--project and --path parameters.
Add a Repositories heading and the repository list component to the repositories feature component’s
template. The contents of organizations-feat-repositories.component.ts should be as follows:
Add the public source component to the repository list component’s template and wrap it in a Primer
Box Row like so:
Inside the Box, we should see the text pubilc-source works! in our browser.
Add TopicsComponent to the imports array of the public source component’s template, then add the
topics component to the end of its template:
1 <gitropolis-orgs-topics></gitropolis-orgs-topics>
Change the contents of the topics.component.ts file to the following to present a static list of topics:
Creating a feature library 58
Conclusion
In our browser, we now see the following:
Creating a feature library 59
If your browser shows something else, go through the steps in this chapter again before moving on
to the next chapter.
In this chapter, we learned to create a feature library by generating the repositories feature library.
We created and exported the repositories feature component and added a top-level feature route to
the Gitropolis application.
As a first step, we list the nrwl/nx repository using static data in the public source component which
itself uses the topics component. We list a repository’s name, visibility (Public), description, and
topics. The repository list is wrapped in a Primer Box and the public source component is wrapped
in a Box row. The Public visibility uses a secondary Primer Label while the topics use Issue labels.
That is a good first step. In the next chapter, we create a data access library in order to load the data
for a single repository dynamically.
Creating a data access library
Having prepared our repositories feature with static data for a single repository, we create a data
access library in this chapter to support loading data dynamically.
In this chapter, we:
Similar to generating a feature library–or any other library type–the name passed to the nx
generate library command follows the format <library-type>[-<library-name>], in this case
data-access-api.
Delete the generated Angular module and remove its re-export in the data access library’s entry
point. Temporarily create an empty entry point by replacing the file contents of index.ts with the
following:
1 export {};
We create an Octokit singleton service by providing a factory to a dependency injection token. Create
an octokit.token.ts file with the following contents:
15 subscriber.next(value);
16 subscriber.complete();
17 })
18 .catch((error: unknown) => {
19 if (subscriber.closed) {
20 return;
21 }
22
23 subscriber.error(error);
24 });
25
26 return () => {
27 abortController.abort();
28 };
29 });
30 }
Do not worry too much about this implementation. We will use the operator and discuss its API
shortly.
Add an byOrganization method by replacing the file contents of repository-api.service with the
following:
We pass octokitToken to the inject function to resolve the singleton instance of the Octokit client.
The byOrganization method passes a promise factory to our abortable operator to defer the creation
of the promise until an observer subscribes. Our promise factory receives an AbortSignal which we
name signal and forward to the request.signal option for the Octokit#rest.repos.listForOrg
method.
We pass the name of an organization that we want to list repositories for. Except for sorting
repositories by the date a commit was last pushed, we use default options meaning that we receive
a single page of results with a maximum of 30 results. To avoid introducing additional complexity,
we only list the first page rather than supporting pagination.
Our Repository domain model contains the repository description, name, and topics as well as the
name of the organization it belongs to. We choose to declare readonly access to all properties to
help enforce immutability.
The Repositories type is a shorthand for the ReadonlyArray<Repository> collection type which is
also expressed as readonly Repository[].
Re-export the repository domain model and collection type in our data access library’s entry point.
Replace the file contents of index.ts with the following:
RxAngular State is now ready to use. It does not require any global configuration.
Open the generated file and start by adding the RepositoryState interface which describes the
internal state of the service:
Creating a data access library 65
The state service contains organization and repositories properties internally. Our application
will pass the organization name when selected by a user.
Note: We keep the { providedIn: 'root' } option for the Injectable decorator so that
repositories are cached locally in memory when we return to the repository list after
having navigated to a different route. Another options is totie this service to the lifecycle
of a component by adding it to a Component.providers metadata option, for example
to the repository list component. This would clear loaded repositories when navigating
away from the component’s route.
Now, we connect a data source to the repositories state property which loads repositories when a
new organization state property is emitted:
16 constructor() {
17 super();
18
19 this.connect(
20 'repositories',
21 this.select('organization').pipe(
22 switchMap((organization) =>
23 this.#api.byOrganization(organization).pipe(
24 map((response) =>
25 response.map(
26 ({ description, name, topics }): Repository => ({
27 description: description ?? '',
28 name,
29 organization,
30 topics: topics ?? [],
31 })
32 )
33 )
34 )
35 )
36 )
37 );
38 }
39 }
We use the repository API service to load the public repositories of the specified organization and
map the response to our repository domain model, stored in the repositories property state and
emitted to subscribers of that property. A null description is converted to an empty string while a
null list of topics is converted to an empty array.
We use the switchMap operator to cancel any in-flight requests when a new organization is selected.
This is possible thanks to the abortable RxJS operator we added to the repository API service earlier
in the Implementing the repository API service section.
Now, re-export the repository state service in the entry point of our organizations data access library
to make it consumable from other libraries in our Nx workspace. Remember that this is done in the
library’s index.ts barrel file as seen in the following code listing:
This is the public API of the organizations data access library. The repository API service is
intentionally hidden from other libraries.
Creating a data access library 67
Conclusion
In this chapter, we created our first data access library located in the organizations domain.
We installed the Octokit client to use the GitHub REST API. Octokit uses promises. One of the
benefits of using RxJS is that we can cancel side effects such as HTTP requests as needed. Unlike
promises, RxJS observables are lazily evaluated. To enable both of these features in our data access
library, we created the abortable RxJS operator which internally sets up an AbortController and
exposes its AbortSignal to support setting up promises by wrapping them in a factory function.
We generated the repository API service and added the byOrganization method which uses the
abortable operator to pass an abort signal to the Octokit REST client.
We created a Repository domain model and exported it with the collection type Repositories, a
shorthand for a read-only array of repositories.
In addition to the domain model, we re-export the repository state service which we implemented
using the RxAngular State package. The service has a built-in side effect that loads public repositories
using the repository API service every time an organization is selected. Consumers are able to
subscribe to repositories but we are going to need to connect a data source for organizations for
this to work.
In the next chapter, we select an organization based on the URL a user navigates to and we pass the
dynamically loaded repository list to our repositories feature, replacing the static repository listing
we used in the chapter Creating a feature library.
Loading repositories dynamically
In the chapter Creating a feature library, we created the repositories feature. In the previous chapter,
we created the organizations data access library that exports the repository state service. With that
in place, we are ready to dynamically load repositories and display them in our repositories feature.
In this chapter, we:
• Select an organization based on the URL a user navigates to which in turn loads the
organization’s repositories
• Replace the static repository listing by passing the dynamically loaded repositories to our
repository list component and its child components
We change the implementations of the components in our repositories feature to accept repository
domain models, starting from the repository list component and ending at the topics component.
16
17 constructor() {
18 this.#store.connect('organization', this.#organizationRouteParameter$);
19 }
20 }
We inject the ActivatedRoute service to access its ParamMap. The route path for the repositories
feature is orgs/:organization/repositories. We access the organization route parameter from
the ParamMap and filter out null values to prevent them from entering our repository state.
In the repository list component’s constructor, we call RepositoryStateService#connect, passing
'organization' for the state property we want to target followed by the organization route
parameter observable that we stored in the #organizationRouteParameter$ property.
1 // (...)
2 export class RepositoryListComponent {
3 #store = inject(RepositoryStateService);
4 // (...)
5 repositories$ = this.#store.select('repositories');
6 }
With the selected organization’s repositories wrapped in an observable, we use Angular’s AsyncPipe
to loop over each Repository domain object and create a public source component for them:
1 @Component({
2 selector: 'gitropolis-orgs-repository-list',
3 standalone: true,
4 imports: [CommonModule, PublicSourceComponent],
5 template: `
6 <h2>Repositories</h2>
7 <div class="Box">
8 <ul>
9 <li *ngFor="let repository of repositories$ | async" class="Box-row">
10 <gitropolis-orgs-public-source></gitropolis-orgs-public-source>
Loading repositories dynamically 70
11 </li>
12 </ul>
13 </div>
14 `,
15 styles: [],
16 changeDetection: ChangeDetectionStrategy.OnPush,
17 })
18 export class RepositoryListComponent {
19 // (...)
20 }
To use the AsyncPipe and the NgForOf structural directive, we add back the CommonModule to the
imports option for the Component decorator.
The previous screenshot demonstrates that the repository loops repeats the static data as many times
as the number of dynamically loaded repositories.
The next section passes a repository to each public source component through a yet-to-be-defined
input property.
Make sure to remove the static description and name properties. The application breaks temporarily
but that is okay.
To pass a repository to each public source component, change the template of the repository list
component to the following:
Next, we change the public source component so that it supports a null input value and hides the
paragraph element when a repository has no description:
Null values are supported by using the safe navigation template operator (?.). The paragraph element
is hidden based on the hasDescription property using the NgIf structural directive. Make sure to
add back the CommonModule to the imports option of the Component decorator to support using NgIf.
In the previous screenshot, we see that the list of repositories is dynamically loaded while the static
repository topics are still repeated.
Our final state change to the repositories feature is to accept a dynamic list of repository topics in
the topics component. This is implemented in the next section.
Go back to the public source component and modify its template so that it passes repository topics
to the topics component through its topics input property:
The safe navigation (?.) and nullish coalescing (??) template operators are used to support a null
value passed to the public source component by the repository list component which uses AsyncPipe.
When null is passed to the repository input property, we pass an empty array to the topics
component’s topics input property.
Loading repositories dynamically 74
The previous screenshot shows dynamically loaded repository topics. If you are used to browsing
repositories on GitHub, you might be surprised to see the long list of repository topics for the nx
repository. Additionally, a repository without topics takes up a blank line in the list. We address
these issues in the next section.
This is done to prevent the topics component from creating a blank line in the repository list
component.
Next, we remove all but the first 7 topics from the list to prevent visual overflow.
Note: GitHub selects 7 topics based on criteria such as global topic popularity. Gitropolis
keeps it simple.
arrayEquals compares the elements of two arrays by reference to determine whether the two arrays
can be considered equal.
Now, we replace the topics input property with the following properties:
Loading repositories dynamically 76
Topics are now stored in the #topics backing field. Every time an array of strings is passed to the
topics input property, we select the first 7 topics, compare them to the topics in the backing field
and store them if they are different from the previously-stored topics.
Loading repositories dynamically 77
The previous screenshot shows the repository list visually optimized concerning repository topics.
At most, 7 topics are displayed. No blank line is displayed when a repository has no topics.
Conclusion
In this chapter, we connected the organization route parameter to the repository state which triggers
the dynamic loading of repositories when a user navigates to the feature route.
We passed the dynamically loaded repositories to the repository list component which loops over
them and forwards each repository domain object to the public source component. We made visual
improvements by removing blank space for repositories without a description.
The public source component passes the repository topics to the topics component which loops over
them. The topics component now has visual optimizations to prevent it from taking up a blank line
for repositories without topics. Additionally, it displays a maximum of 7 topics to prevent visual
overflow in the repository list.
We have been using the nrwl GitHub organization as an example by navigating to
http://localhost:4200/orgs/nrwl/repositories but other valid GitHub organizations also work,
for example angular at http://localhost:4200/orgs/nrwl/repositories as seen in the following
screenshot:
Loading repositories dynamically 78