Building Angular Apps with the Nx Standalone Projects Setup
In this tutorial you'll learn how to use Angular with Nx in a "standalone" (non-monorepo) setup. Not to be confused with the "Angular Standalone API", a standalone project in Nx is a non-monorepo setup where you have a single application at the root level. This setup is very similar to what the Angular CLI gives you.
What will you learn?
- how to create a new standalone (single-project) Nx workspace setup for Angular
- how to run a single task (i.e. serve your app) or run multiple tasks in parallel
- how to leverage code generators to scaffold components
- how to modularize your codebase and impose architectural constraints for better maintainability
- how to speed up CI with Nx Cloud ⚡
Note, this tutorial sets up a repo with a single application at the root level that breaks out its code into libraries to add structure. If you are looking for an Angular monorepo setup then check out our Angular monorepo tutorial.
Final Code
Here's the source code of the final result for this tutorial.
Example repository/nrwl/nx-recipes/tree/main/angular-standalone
Also, if you prefer learning with a video, join Juri and walk through the tutorial, step by step together.
Creating a new Angular App
Create a new Angular application with the following command:
~❯
npx create-nx-workspace@latest myngapp --preset=angular-standalone
1
2NX Let's create a new workspace [https://nx.dev/getting-started/intro]
3
4✔ Which bundler would you like to use? · esbuild
5✔ Default stylesheet format · css
6✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? · No
7✔ Test runner to use for end to end (E2E) tests · cypress
8✔ Which CI provider would you like to use? · github
9
You get asked a few questions that help Nx preconfigure your new Angular application. These include:
- Angular specific questions, such as which bundler to use, whether to enable server-side rendering and which stylesheet format to use
- General Nx questions, such as whether to enable remote caching with Nx Cloud. Nx comes with built-in local caching. If you want to benefit from this cache in CI, you can enable remote caching which will set up Nx Cloud. This is also a prerequisite for enabling distributed task execution. We'll explore this later in the tutorial.
For the sake of this tutorial, let's respond to all the questions with the default response.
The create-nx-workspace
command generates the following structure:
1└─ myngapp
2 ├─ .vscode
3 │ └─ extensions.json
4 ├─ e2e
5 │ ├─ ...
6 │ ├─ project.json
7 │ ├─ src
8 │ │ ├─ e2e
9 │ │ │ └─ app.cy.ts
10 │ │ ├─ ...
11 │ └─ tsconfig.json
12 ├─ src
13 │ ├─ app
14 │ │ ├─ app.component.css
15 │ │ ├─ app.component.html
16 │ │ ├─ app.component.spec.ts
17 │ │ ├─ app.component.ts
18 │ │ ├─ app.config.ts
19 │ │ ├─ app.routes.ts
20 │ │ └─ nx-welcome.component.ts
21 │ ├─ assets
22 │ ├─ favicon.ico
23 │ ├─ index.html
24 │ ├─ main.ts
25 │ ├─ styles.css
26 │ └─ test-setup.ts
27 ├─ jest.config.ts
28 ├─ jest.preset.js
29 ├─ nx.json
30 ├─ package-lock.json
31 ├─ package.json
32 ├─ project.json
33 ├─ README.md
34 ├─ tsconfig.app.json
35 ├─ tsconfig.editor.json
36 ├─ tsconfig.json
37 └─ tsconfig.spec.json
38
The setup includes:
- a new Angular application at the root of the Nx workspace (
src/app
) - a Cypress based set of e2e tests (
e2e/
) - Prettier preconfigured
- ESLint & Angular ESLint preconfigured
- Jest preconfigured
Compared to the Angular CLI, you might notice the addition of an nx.json
file and the absence of an angular.json
file. Instead of the angular.json
file there is a project.json
file. Each file is described below:
File | Description |
---|---|
nx.json | This is where we can fine-tune how Nx works, define the cacheable operations, our task pipelines as well as defaults for the Nx generators. Find more details in the reference docs. |
project.json | Nx uses this file to define targets that can be run, similar to how the Angular CLI uses the angular.json file. If you're familiar with the Angular CLI you should have no difficulty navigating the project.json file. If you're curious how the two compare, you can learn more in the Nx and Angular CLI comparision article. The project-configuration documentation page has more details on how to use the project.json file. |
Serving the App
The most common tasks are already defined in the package.json
file:
1{
2 "name": "myngapp",
3 "scripts": {
4 "start": "nx serve",
5 "build": "nx build",
6 "test": "nx test"
7 }
8 ...
9}
10
To serve your new Angular application, just run: npm start
. Alternatively you can directly use Nx by running:
❯
nx serve
Your application should be served at http://localhost:4200.
Nx uses the following syntax to run tasks:
Manually Defined Tasks
The project tasks are defined in the project.json
file:
1{
2 "name": "myngapp",
3 ...
4 "targets": {
5 "build": { ... },
6 "serve": { ... },
7 "extract-i18n": { ... },
8 "lint": { ... },
9 "test": { ... },
10 "serve-static": { ... },
11 },
12}
13
Each target contains a configuration object that tells Nx how to run that target.
1{
2 "name": "myngapp",
3 ...
4 "targets": {
5 "serve": {
6 "executor": "@angular-devkit/build-angular:dev-server",
7 "configurations": {
8 "production": {
9 "browserTarget": "myngapp:build:production"
10 },
11 "development": {
12 "browserTarget": "myngapp:build:development"
13 }
14 },
15 "defaultConfiguration": "development"
16 },
17 ...
18 },
19}
20
The most critical parts are:
executor
- This corresponds to thebuilder
property in an Angular CLI workspace. You can use Angular builders or executors from Nx plugins.options
- these are additional properties and flags passed to the executor function to customize it
Learn more about how to run tasks with Nx.
Testing and Linting
Our current setup not only has targets for serving and building the Angular application, but also has targets for unit testing, e2e testing and linting. The test
and lint
targets are defined in the application project.json
file, while the e2e
target is inferred from the e2e/cypress.config.ts
file. We can use the same syntax as before to run these tasks:
1nx test # runs unit tests using Jest
2nx lint # runs linting with ESLint
3nx e2e e2e # runs e2e tests from the e2e project with Cypress
4
Inferred Tasks
Nx identifies available tasks for your project from tooling configuration files, package.json
scripts and the targets defined in project.json
. All tasks from the myngapp
project are defined in its project.json
file, but the companion e2e
project has its tasks inferred from configuration files. To view the tasks that Nx has detected, look in the Nx Console, Project Details View or run:
❯
nx show project e2e --web
e2e
Root: e2e
Type:application
Targets
e2e-ci
nx:noop
Cacheablee2e-ci--src/e2e/app.cy.ts
cypress run --env webServerCommand="nx run myngapp:serve-static" --spec src/e2e/app.cy.ts
Cacheable
e2e
cypress run
Cacheablelint
eslint .
Cacheable
If you expand the e2e
task, you can see that it was created by the @nx/cypress
plugin by analyzing the e2e/cypress.config.ts
file. Notice the outputs are defined as:
1[
2 "{workspaceRoot}/dist/cypress/e2e/videos",
3 "{workspaceRoot}/dist/cypress/e2e/screenshots"
4]
5
This value is being read from the videosFolder
and screenshotsFolder
defined by the nxE2EPreset
in your e2e/cypress.config.ts
file. Let's change their value in your e2e/cypress.config.ts
file:
1// ...
2export default defineConfig({
3 e2e: {
4 ...nxE2EPreset(__filename, {
5 // ...
6 }),
7 baseUrl: 'http://localhost:4200',
8 videosFolder: '../dist/cypress/e2e/videos-changed',
9 screenshotsFolder: '../dist/cypress/e2e/screenshots-changed',
10 },
11});
12
Now if you look at the project details view again, the outputs for the e2e
target will be:
1[
2 "{workspaceRoot}/dist/cypress/e2e/videos-changed",
3 "{workspaceRoot}/dist/cypress/e2e/screenshots-changed"
4]
5
This feature ensures that Nx will always cache the correct files.
You can also override the settings for inferred tasks by modifying the targetDefaults
in nx.json
or setting a value in your project.json
file. Nx will merge the values from the inferred tasks with the values you define in targetDefaults
and in your specific project's configuration.
Running Multiple Tasks
In addition to running individual tasks, you can also run multiple tasks in parallel using the following syntax:
myngapp❯
nx run-many -t test lint e2e
1
2✔ nx run e2e:lint (1s)
3✔ nx run myngapp:lint (1s)
4✔ nx run myngapp:test (2s)
5✔ nx run e2e:e2e (6s)
6
7——————————————————————————————————————————————————————
8
9NX Successfully ran targets test, lint, e2e for 2 projects (8s)
10
Caching
One thing to highlight is that Nx is able to cache the tasks you run.
Note that all of these targets are automatically cached by Nx. If you re-run a single one or all of them again, you'll see that the task completes immediately. In addition, (as can be seen in the output example below) there will be a note that a matching cache result was found and therefore the task was not run again.
myngapp❯
nx run-many -t test lint e2e
1
2✔ nx run myngapp:lint [existing outputs match the cache, left as is]
3✔ nx run e2e:lint [existing outputs match the cache, left as is]
4✔ nx run myngapp:test [existing outputs match the cache, left as is]
5✔ nx run e2e:e2e [existing outputs match the cache, left as is]
6
7———————————————————————————————————————————————————————
8
9 Successfully ran targets test, lint, e2e for 2 projects (143ms)
10
11Nx read the output from the cache instead of running the command for 4 out of 4 tasks.
12
Not all tasks might be cacheable though. You can configure the cache
properties in the targets under targetDefaults
in the nx.json
file. You can also learn more about how caching works.
Creating New Components
Similar to the Angular CLI, Nx comes with code generation abilities. What the Angular CLI calls "Schematics", Nx calls "Generators".
Generators allow you to easily scaffold code, configuration or entire projects. To see what capabilities the @nx/angular
plugin ships with, run the following command and inspect the output:
myngapp❯
npx nx list @nx/angular
1
2NX Capabilities in @nx/angular:
3
4NX Capabilities in @nx/angular:
5
6 GENERATORS
7
8 add-linting : Adds linting configuration to an Angular project.
9 application : Creates an Angular application.
10 component : Generate an Angular Component.
11 ...
12 library : Creates an Angular library.
13 library-secondary-entry-point : Creates a secondary entry point for an Angular publishable library.
14 remote : Generate a Remote Angular Module Federation Application.
15 move : Moves an Angular application or library to another folder within the workspace and updates the project configuration.
16 convert-to-with-mf : Converts an old micro frontend configuration...
17 host : Generate a Host Angular Module Federation Application.
18 ng-add : Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace.
19 ngrx : Adds NgRx support to an application or library.
20 scam-to-standalone : Convert an existing Single Component Angular Module (SCAM) to a Standalone Component.
21 scam : Generate a component with an accompanying Single Component Angular Module (SCAM).
22 scam-directive : Generate a directive with an accompanying Single Component Angular Module (SCAM).
23 scam-pipe : Generate a pipe with an accompanying Single Component Angular Module (SCAM).
24 setup-mf : Generate a Module Federation configuration for a given Angular application.
25 setup-ssr : Generate Angular Universal (SSR) setup for an Angular application.
26 setup-tailwind : Configures Tailwind CSS for an application or a buildable/publishable library.
27 stories : Creates stories/specs for all components declared in a project.
28 storybook-configuration : Adds Storybook configuration to a project.
29 cypress-component-configuration : Setup Cypress component testing for a project.
30 web-worker : Creates a Web Worker.
31 directive : Generate an Angular directive.
32 ngrx-feature-store : Adds an NgRx Feature Store to an application or library.
33 ngrx-root-store : Adds an NgRx Root Store to an application.
34 pipe : Generate an Angular Pipe
35
36 EXECUTORS/BUILDERS/
37
38 delegate-build : Delegates the build to a different target while supporting incremental builds.
39 ...
40
If you prefer a more integrated experience, you can install the "Nx Console" extension for your code editor. It has support for VSCode, IntelliJ and ships a LSP for Vim. Nx Console provides autocompletion support in Nx configuration files and has UIs for browsing and running generators.
More info can be found in the integrate with editors article.
Run the following command to generate a new "hello-world" component. Note how we append --dry-run
to first check the output.
myngapp❯
npx nx g @nx/angular:component hello-world --directory=src/app/hello-world --standalone --dry-run
1NX Generating @nx/angular:component
2
3CREATE src/app/hello-world/hello-world.component.css
4CREATE src/app/hello-world/hello-world.component.html
5CREATE src/app/hello-world/hello-world.component.spec.ts
6CREATE src/app/hello-world/hello-world.component.ts
7
8NOTE: The "dryRun" flag means no changes were made.
9
As you can see it generates a new component in the app/hello-world/
folder. If you want to actually run the generator, remove the --dry-run
flag.
1import { Component } from '@angular/core';
2import { CommonModule } from '@angular/common';
3
4({
5 selector: 'myngapp-hello-world',
6 standalone: true,
7 imports: [CommonModule],
8 templateUrl: './hello-world.component.html',
9 styleUrls: ['./hello-world.component.css'],
10})
11export class HelloWorldComponent {}
12
Building the App for Deployment
If you're ready and want to ship your application, you can build it using
myngapp❯
npx nx build
1> nx run myngapp:build:production
2
3✔ Browser application bundle generation complete.
4✔ Copying assets complete.
5✔ Index html generation complete.
6
7Initial Chunk Files | Names | Raw Size | Estimated Transfer Size
8main.afa99fe9f64fbdd9.js | main | 193.58 kB | 51.57 kB
9polyfills.1acfd3f58d94d542.js | polyfills | 32.98 kB | 10.66 kB
10runtime.37059233034b21c2.js | runtime | 892 bytes | 515 bytes
11styles.ef46db3751d8e999.css | styles | 0 bytes | -
12
13 | Initial Total | 227.44 kB | 62.73 kB
14
15Build at: 2023-05-23T14:00:31.981Z - Hash: 9086e92ce0bfefca - Time: 5228ms
16
17——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
18
19Successfully ran target build for project myngapp (7s)
20
All the required files will be placed in the dist/myngapp
folder and can be deployed to your favorite hosting provider.
You're ready to go!
In the previous sections you learned about the basics of using Nx, running tasks and navigating an Nx workspace. You're ready to ship features now!
But there's more to learn. You have two possibilities here:
- Jump to the next steps section to find where to go from here or
- keep reading and learn some more about what makes Nx unique when working with Angular.
Modularizing your Angular App with Local Libraries
When you develop your Angular application, usually all your logic sits in the app
folder. Ideally separated by various folder names which represent your "domains". As your app grows, this becomes more and more monolithic though.
The following structure is a common example of this kind of monolithic code organization:
1└─ myngapp
2 ├─ ...
3 ├─ src
4 │ ├─ app
5 │ │ ├─ products
6 │ │ ├─ cart
7 │ │ ├─ ui
8 │ │ ├─ ...
9 │ │ └─ app.component.ts
10 │ ├─ ...
11 │ └─ main.ts
12 ├─ ...
13 ├─ package.json
14 ├─ ...
15
Nx allows you to separate this logic into "local libraries". The main benefits include
- better separation of concerns
- better reusability
- more explicit "APIs" between your "domain areas"
- better scalability in CI by enabling independent test/lint/build commands for each library
- better scalability in your teams by allowing different teams to work on separate libraries
Creating Local Libraries
Let's assume our domain areas include products
, orders
and some more generic design system components, called ui
. We can generate a new library for each of these areas using the Angular library generator:
1nx g @nx/angular:library products --directory=modules/products --standalone
2nx g @nx/angular:library orders --directory=modules/orders --standalone
3nx g @nx/angular:library shared-ui --directory=modules/shared/ui --standalone
4
Note how we use the --directory
flag to place the libraries into a subfolder. You can choose whatever folder structure you like, even keep all of them at the root-level.
Running the above commands should lead to the following directory structure:
1└─ myngapp
2 ├─ ...
3 ├─ e2e/
4 ├─ modules
5 │ ├─ products
6 │ │ ├─ .eslintrc.json
7 │ │ ├─ README.md
8 │ │ ├─ jest.config.ts
9 │ │ ├─ project.json
10 │ │ ├─ src
11 │ │ │ ├─ index.ts
12 │ │ │ ├─ lib
13 │ │ │ │ └─ products
14 │ │ │ │ ├─ products.component.css
15 │ │ │ │ ├─ products.component.html
16 │ │ │ │ ├─ products.component.spec.ts
17 │ │ │ │ └─ products.component.ts
18 │ │ │ └─ test-setup.ts
19 │ │ ├─ tsconfig.json
20 │ │ ├─ tsconfig.lib.json
21 │ │ └─ tsconfig.spec.json
22 │ ├─ orders
23 │ │ ├─ ...
24 │ │ ├─ src
25 │ │ │ ├─ index.ts
26 │ │ │ ├─ lib
27 │ │ │ │ └─ orders
28 │ │ │ │ ├─ ...
29 │ │ │ │ └─ orders.component.ts
30 │ │ ├─ ...
31 │ └─ shared
32 │ └─ ui
33 │ ├─ ...
34 │ ├─ src
35 │ │ ├─ index.ts
36 │ │ ├─ lib
37 │ │ │ └─ shared-ui
38 │ │ │ ├─ ...
39 │ │ │ └─ shared-ui.component.ts
40 │ └─ ...
41 ├─ ...
42 ├─ src
43 │ ├─ app
44 │ │ ├─ ...
45 │ │ ├─ app.component.ts
46 │ ├─ ...
47 ├─ ...
48
Each of these libraries
- has its own
project.json
file with corresponding targets you can run (e.g. running tests for just orders:nx test orders
) - has a dedicated
index.ts
file which is the "public API" of the library - is mapped in the
tsconfig.base.json
at the root of the workspace
Importing Libraries into the Angular Application
All libraries that we generate automatically have aliases created in the root-level tsconfig.base.json
.
1{
2 "compilerOptions": {
3 ...
4 "paths": {
5 "@myngapp/orders": ["modules/orders/src/index.ts"],
6 "@myngapp/products": ["modules/products/src/index.ts"],
7 "@myngapp/shared-ui": ["modules/shared/ui/src/index.ts"]
8 },
9 ...
10 },
11}
12
Hence we can easily import them into other libraries and our Angular application. For example: let's use our existing ProductsComponent
in modules/products/src/lib/products/
:
1import { Component } from '@angular/core';
2import { CommonModule } from '@angular/common';
3
4({
5 selector: 'myngapp-products',
6 standalone: true,
7 imports: [CommonModule],
8 templateUrl: './products.component.html',
9 styleUrl: './products.component.css',
10})
11export class ProductsComponent {}
12
Make sure the ProductsComponent
is exported via the index.ts
file of our products
library (which it should already be). The modules/products/src/index.ts
file is the public API for the products
library with the rest of the workspace. Only export what's really necessary to be usable outside the library itself.
1export * from './lib/products/products.component';
2
We're ready to import it into our main application now. If you opted into generating a router configuration when setting up the Nx workspace initially, you should have an app.routes.ts
file in your app
folder. If not, create it and configure the Angular router.
Configure the routing as follows:
1import { Route } from '@angular/router';
2import { NxWelcomeComponent } from './nx-welcome.component';
3
4export const appRoutes: Route[] = [
5 {
6 path: '',
7 component: NxWelcomeComponent,
8 pathMatch: 'full',
9 },
10 {
11 path: 'products',
12 loadComponent: () =>
13 import('@myngapp/products').then((m) => m.ProductsComponent),
14 },
15];
16
As part of this step, we should also remove the NxWelcomeComponent
from the AppComponent.imports
declaration so that it is just loaded over the routing mechanism. The app.component.html
should just have the <router-outlet>
left:
1<router-outlet></router-outlet>
2
If you now navigate to http://localhost:4200/products you should see the ProductsComponent
being rendered.
Let's do the same process for our orders
library. Import the OrdersComponent
into the app.routes.ts
:
1import { Route } from '@angular/router';
2import { NxWelcomeComponent } from './nx-welcome.component';
3
4export const appRoutes: Route[] = [
5 {
6 path: '',
7 component: NxWelcomeComponent,
8 pathMatch: 'full',
9 },
10 {
11 path: 'products',
12 loadComponent: () =>
13 import('@myngapp/products').then((m) => m.ProductsComponent),
14 },
15 {
16 path: 'orders',
17 loadComponent: () =>
18 import('@myngapp/orders').then((m) => m.OrdersComponent),
19 },
20];
21
Similarly, navigating to http://localhost:4200/orders should now render the OrdersComponent
.
A couple of notes:
- both the
ProductsComponent
andOrdersComponent
are lazy loaded - you could go even further and configure routes within the libraries and only import and attach those routes to the application routing mechanism.
Visualizing your Project Structure
Nx automatically detects the dependencies between the various parts of your workspace and builds a project graph. This graph is used by Nx to perform various optimizations such as determining the correct order of execution when running tasks like nx build
, identifying affected projects and more. Interestingly you can also visualize it.
Just run:
❯
nx graph
You should be able to see something similar to the following in your browser (hint: click the "Show all projects" button).
Notice how shared-ui
is not yet connected to anything because we didn't import it in any of our projects. Also the arrows to orders
and products
are dashed because we're using lazy imports.
Exercise for you: change the codebase so that shared-ui
is used by orders
and products
. Note: you need to restart the nx graph
command to update the graph visualization or run the CLI command with the --watch
flag.
Imposing Constraints with Module Boundary Rules
Once you modularize your codebase you want to make sure that the modules are not coupled to each other in an uncontrolled way. Here are some examples of how we might want to guard our small demo workspace:
- we might want to allow
orders
to import fromshared-ui
but not the other way around - we might want to allow
orders
to import fromproducts
but not the other way around - we might want to allow all libraries to import the
shared-ui
components, but not the other way around
When building these kinds of constraints you usually have two dimensions:
- type of project: what is the type of your library. Example: "feature" library, "utility" library, "data-access" library, "ui" library (see library types)
- scope (domain) of the project: what domain area is covered by the project. Example: "orders", "products", "shared" ... this really depends on the type of product you're developing
Nx comes with a generic mechanism that allows you to assign "tags" to projects. "tags" are arbitrary strings you can assign to a project that can be used later when defining boundaries between projects. For example, go to the project.json
of your orders
library and assign the tags type:feature
and scope:orders
to it.
1{
2 ...
3 "tags": ["type:feature", "scope:orders"],
4 ...
5}
6
Then go to the project.json
of your products
library and assign the tags type:feature
and scope:products
to it.
1{
2 ...
3 "tags": ["type:feature", "scope:products"],
4 ...
5}
6
Finally, go to the project.json
of the shared-ui
library and assign the tags type:ui
and scope:shared
to it.
1{
2 ...
3 "tags": ["type:ui", "scope:shared"],
4 ...
5}
6
Notice how we assign scope:shared
to our UI library because it is intended to be used throughout the workspace.
Next, let's come up with a set of rules based on these tags:
type:feature
should be able to import fromtype:feature
andtype:ui
type:ui
should only be able to import fromtype:ui
scope:orders
should be able to import fromscope:orders
,scope:shared
andscope:products
scope:products
should be able to import fromscope:products
andscope:shared
To enforce the rules, Nx ships with a custom ESLint rule. Open the .eslintrc.base.json
at the root of the workspace and add the following depConstraints
in the @nx/enforce-module-boundaries
rule configuration:
1{
2 ...
3 "overrides": [
4 {
5 ...
6 "rules": {
7 "@nx/enforce-module-boundaries": [
8 "error",
9 {
10 "enforceBuildableLibDependency": true,
11 "allow": [],
12 "depConstraints": [
13 {
14 "sourceTag": "*",
15 "onlyDependOnLibsWithTags": ["*"]
16 },
17 {
18 "sourceTag": "type:feature",
19 "onlyDependOnLibsWithTags": ["type:feature", "type:ui"]
20 },
21 {
22 "sourceTag": "type:ui",
23 "onlyDependOnLibsWithTags": ["type:ui"]
24 },
25 {
26 "sourceTag": "scope:orders",
27 "onlyDependOnLibsWithTags": [
28 "scope:orders",
29 "scope:products",
30 "scope:shared"
31 ]
32 },
33 {
34 "sourceTag": "scope:products",
35 "onlyDependOnLibsWithTags": ["scope:products", "scope:shared"]
36 },
37 {
38 "sourceTag": "scope:shared",
39 "onlyDependOnLibsWithTags": ["scope:shared"]
40 }
41 ]
42 }
43 ]
44 }
45 },
46 ...
47 ]
48}
49
To test it, go to your modules/products/src/lib/products/products.component.ts
file and import the OrderComponent
from the orders
project:
1import { Component } from '@angular/core';
2import { CommonModule } from '@angular/common';
3
4// 👇 this import is not allowed
5import { OrdersComponent } from '@myngapp/orders';
6
7@Component({
8 selector: 'myngapp-products',
9 standalone: true,
10 imports: [CommonModule, OrdersComponent],
11 templateUrl: './products.component.html',
12 styleUrls: ['./products.component.css'],
13})
14export class ProductsComponent {}
15
If you lint your workspace you'll get an error now:
❯
nx run-many -t lint
1✖ nx run products:lint
2 Linting "products"...
3
4 /Users/juri/nrwl/content/myngapp/modules/products/src/lib/products/products.component.ts
5 3:1 error A project tagged with "scope:products" can only depend on libs tagged with "scope:products", "scope:shared" @nx/enforce-module-boundaries
6
7 ✖ 1 problem (1 error, 0 warnings)
8
9 Lint errors found in the listed files.
10
11✔ nx run orders:lint (1s)
12✔ nx run myngapp:lint (1s)
13✔ nx run e2e:lint (682ms)
14✔ nx run shared-ui:lint (797ms)
15
16—————————————————————————————————————————————————————————————————————
17
18NX Ran target lint for 5 projects (2s)
19
20✔ 4/5 succeeded [0 read from cache]
21
22✖ 1/5 targets failed, including the following:
23 - nx run products:lint
24
25
If you have the ESLint plugin installed in your IDE you should immediately see an error:
Learn more about how to enforce module boundaries.
Migrating to a Monorepo
When you are ready to add another application to the repo, you'll probably want to move myngapp
to its own folder. To do this, you can run the convert-to-monorepo
generator or manually move the configuration files.
You can also go through the full Angular monorepo tutorial
Fast CI ⚡
Repository with NxMake sure you have completed the previous sections of this tutorial before starting this one. If you want a clean starting point, you can check out the reference code as a starting point.
This tutorial walked you through how Nx can improve the local development experience, but the biggest difference Nx makes is in CI. As repositories get bigger, making sure that the CI is fast, reliable and maintainable can get very challenging. Nx provides a solution.
- Nx reduces wasted time in CI with the
affected
command. - Nx Replay's remote caching will reuse task artifacts from different CI executions making sure you will never run the same computation twice.
- Nx Agents efficiently distribute tasks across machines ensuring constant CI time regardless of the repository size. The right number of machines is allocated for each PR to ensure good performance without wasting compute.
- Nx Atomizer automatically splits large e2e tests to distribute them across machines. Nx can also automatically identify and rerun flaky e2e tests.
Connect to Nx Cloud
Nx Cloud is a companion app for your CI system that provides remote caching, task distribution, e2e tests deflaking, better DX and more.
Now that we're working on the CI pipeline, it is important for your changes to be pushed to a GitHub repository.
- Commit your existing changes with
git add . && git commit -am "updates"
- Create a new GitHub repository
- Follow GitHub's instructions to push your existing code to the repository
When we set up the repository at the beginning of this tutorial, we chose to use GitHub Actions as a CI provider. This created a basic CI pipeline and configured Nx Cloud in the repository. It also printed a URL in the terminal to register your repository in your Nx Cloud account. If you didn't click on the link when first creating your repository, you can show it again by running:
❯
npx nx connect
Once you click the link, follow the steps provided and make sure Nx Cloud is enabled on the main branch of your repository.
Configure Your CI Workflow
When you chose GitHub Actions as your CI provider at the beginning of the tutorial, create-nx-workspace
created a .github/workflows/ci.yml
file that contains a CI pipeline that will run the lint
, test
, build
and e2e
tasks for projects that are affected by any given PR. Since we are using Nx Cloud, the pipeline will also distribute tasks across multiple machines to ensure fast and reliable CI runs.
If you need to generate a new workflow file for GitHub Actions or other providers, you can do so with this command:
❯
npx nx generate ci-workflow
The key lines in the CI pipeline are:
1name: CI
2# ...
3jobs:
4 main:
5 runs-on: ubuntu-latest
6 steps:
7 - uses: actions/checkout@v4
8 with:
9 fetch-depth: 0
10 # This enables task distribution via Nx Cloud
11 # Run this command as early as possible, before dependencies are installed
12 # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun
13 # Connect your workspace by running "nx connect" and uncomment this
14 - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="build"
15 - uses: actions/setup-node@v3
16 with:
17 node-version: 20
18 cache: 'npm'
19 - run: npm ci --legacy-peer-deps
20 - uses: nrwl/nx-set-shas@v4
21 # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
22 - run: npx nx affected -t lint test build
23
Open a Pull Request
Commit the changes and open a new PR on GitHub.
❯
git add .
❯
git commit -m 'add CI workflow file'
❯
git push origin add-workflow
When you view the PR on GitHub, you will see a comment from Nx Cloud that reports on the status of the CI run.
The See all runs
link goes to a page with the progress and results of tasks that were run in the CI pipeline.
For more information about how Nx can improve your CI pipeline, check out one of these detailed tutorials:
Next Steps
Here's some things you can dive into next:
- Learn more about the underlying mental model of Nx
- Learn about popular generators such as how to setup Tailwind or add Storybook to your UI library
- Learn how to migrate your existing Angular CLI repo to Nx
Also, make sure you
- Join the Official Nx Discord Server to ask questions and find out the latest news about Nx.
- Follow Nx on Twitter to stay up to date with Nx news
- Read our Nx blog
- Subscribe to our Youtube channel for demos and Nx insights