Prerequisites

  1. Node.js 12 is recommended for Nx 11.
  2. The guide assumes Nx CLI version 11.x is installed globally.
  3. Having Angular CLI version 11.x installed globally is also advisable.

Setting up a new Nx Angular workspace with angular-eslint

When starting fresh, you have two workspace presets to choose from: empty or angular. Both paths lead to a fully functional Nx workspace with Angular and ESLint integrated.

Option 1: Use the empty workspace preset

The empty preset generates a workspace.json file at version 2, which aligns with Nx plugins built for Nx 11 and beyond.

  1. Create a new workspace.
    Begin by scaffolding a minimal Nx workspace.

    With NPM CLI:

    npm init nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=empty --no-nx-cloud --package-manager=npm
    

    With PNPM CLI:

    pnpm init nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=empty --no-nx-cloud --package-manager=pnpm
    

    With Yarn CLI:

    yarn create nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=empty --no-nx-cloud --package-manager=yarn
    
  2. Define the base branch for affected commands.
    In 2020, many Git repositories adopted main as the default branch. However, Nx version 11.0.18 still defaults to comparing against master, irrespective of your Git configuration.

    If your default branch is main:

    npx json -I -f nx.json -e "this.affected.defaultBase = 'main';"
    
  3. Remove TSLint.
    TSLint ships with every new Nx workspace, but it's now officially retired. Uninstall the tslint package.

    With NPM CLI:

    npm uninstall tslint
    

    With Yarn CLI:

    yarn remove tslint
    

    An alternative is to add a preinstall script that permanently purges Codelyzer and TSLint, even if generators attempt to reinstall them.

    With NPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(npm uninstall codelyzer || echo ✅ Codelyzer is already removed.) && (npm uninstall tslint || echo ✅ TSLint is already removed.)';"
    npm install
    

    With PNPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(pnpm remove codelyzer || echo ✅ Codelyzer is already removed.) && (pnpm remove tslint || echo ✅ TSLint is already removed.)';"
    pnpm install
    

    With Yarn CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(yarn remove codelyzer || echo ✅ Codelyzer is already removed.) && (yarn remove tslint || echo ✅ TSLint is already removed.)';"
    yarn install
    
  4. Install and configure the @nrwl/angular package.
    This package provides the generators and executors needed for Angular projects within Nx.

    With NPM CLI:

    npm install @nrwl/angular
    nx generate @nrwl/angular:init
    

    With PNPM CLI:

    pnpm add @nrwl/angular
    nx generate @nrwl/angular:init
    

    With Yarn CLI:

    yarn add @nrwl/angular
    nx generate @nrwl/angular:init
    
  5. Activate Angular strict mode.
    We advocate for strict TypeScript and Angular settings. Enable strict mode for both application and library projects.

    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:application'].strict = true;"
    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:library'].strict = true;"
    
  6. Adopt ESLint as the default linter.
    Set ESLint as the linter for all new Angular projects, which automatically adds the necessary angular-eslint plugins.

    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:application'].linter = 'eslint';"
    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:library'].linter = 'eslint';"
    
  7. Choose a unit test runner.
    Nx supports both Jest and Karma for Angular projects. Pick the one that suits your workflow.

    If using Jest:

    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:application'].unitTestRunner = 'jest';"
    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:library'].unitTestRunner = 'jest';"
    

    If using Karma:

    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:application'].unitTestRunner = 'karma';"
    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:library'].unitTestRunner = 'karma';"
    
  8. Choose an end-to-end test runner.
    For e2e testing, Nx offers Cypress and Protractor for Angular applications.

    If using Cypress:

    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:application'].e2eTestRunner = 'cypress';"
    

    If using Protractor:

    npx json -I -f workspace.json -e "this.generators['@nrwl/angular:application'].e2eTestRunner = 'protractor';"
    
  9. Generate an Angular application.
    With the generators configured as above, your new application will come pre-configured with ESLint and angular-eslint.

    nx generate @nrwl/angular:application --name=booking-app --prefix=booking --tags="type:app,scope:booking" --no-interactive
    

    Don't forget to assign project tags to the generated e2e test project.

    npx json -I -f nx.json -e "this.projects['booking-app-e2e'].tags = ['type:e2e','scope:booking'];"
    
  10. Set strict Angular build budgets.
    As of Nx 11.0.18, build budgets don't automatically adapt to Angular strict mode. Apply the same thresholds used by Angular CLI 11 in strict mode.

    The main bundle should warn at 500 KB and error at 1 MB. Component styles should warn at 2 KB and error at 4 KB.

    npx json -I -f workspace.json -e "this.projects['booking-app'].targets.build.configurations.production.budgets = [{ type: 'initial', maximumWarning: '500kb', maximumError: '1mb' }, { type: 'anyComponentStyle', maximumWarning: '2kb', maximumError: '4kb' }];"
    
  11. Remove Codelyzer.
    Angular CLI 11 bundles Codelyzer by default. Since TSLint is now deprecated, it's time to part ways with this package as well.

    With NPM CLI:

    npm uninstall codelyzer
    

    With PNPM CLI:

    pnpm remove codelyzer
    

    With Yarn CLI:

    yarn remove codelyzer
    
  12. Create a workspace library.
    To test the setup for libraries, generate a shared Angular library within the workspace.

    nx generate @nrwl/angular:library feature-flight-search --directory=booking --prefix=booking --tags="type:feature,scope:booking" --buildable --enable-ivy --no-interactive
    

    Leverage Nx 11's improved incremental builds and computation caching by making the library buildable (though not publishable) and compiled with Ivy.

  13. Run the linter.
    Execute the lint target across all projects to confirm that ESLint and angular-eslint are functioning as expected.

    nx run-many --target=lint --all
    

At this point, you have a new Nx workspace containing an Angular app and a shared library. The empty preset ensures you're on workspace configuration version 2, which introduces the terminology of executors, generators, and targets.

Inspect workspace.json to confirm that the lint targets are wired to the @nrwl/linter:eslint executor.

The root .eslintrc.json should reference the @nrwl/nx/typescript plugin. Check the project-level .eslintrc.json files in both the app and library to see the @nrwl/nx/angular, @nrwl/nx/angular-template, and @angular-eslint/template/process-inline-templates plugins listed.

Option 2: Apply the Angular workspace preset

Starting from Nx 11.0.18, the angular workspace preset scaffolds the initial application with angular-eslint included. However, the initial application and end-to-end test projects are generated without honoring any of these flags:

  • create-application
  • e2e-test-runner
  • no-interactive
  • strict
  • tags
  • unit-test-runner

Additionally, the --linter flag is defective — it always responds with the same error message, no matter how it is used:

>  NX   ERROR  Invalid linter

  It must be one of the following:

  eslint
  tslint
Enter fullscreen mode Exit fullscreen mode

Even so, ESLint with angular-eslint remains the default linter.

This forces us to remove the initial projects when the defaults are not acceptable. The process then involves adjusting the schematics configuration and recreating both the application and end-to-end test projects from scratch.

  1. Start by creating an Nx Angular workspace.

    Via the NPM CLI:

    npm init nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --linter=eslint --no-nx-cloud --style=css --package-manager=npm
    

    Via the PNPM CLI:

    pnpm init nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --linter=eslint --no-nx-cloud --style=css --package-manager=pnpm
    

    Via the Yarn CLI:

    yarn create nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --linter=eslint --no-nx-cloud --style=css --package-manager=yarn
    
  2. Adjust the base branch for affected commands.
    If you adopted main as your default Git branch this year, note that Nx 11.0.18 still compares against master by default, regardless of what Git uses as its default branch.

    If your default branch is main:

    npx json -I -f nx.json -e "this.affected.defaultBase = 'main';"
    
  3. Remove Codelyzer and TSLint.
    The angular preset in Nx 11 still pulls in Codelyzer. With TSLint now being fully end-of-life, these dependencies are obsolete. Uninstall the codelyzer and tslint packages.

    Via the NPM CLI:

    npm uninstall codelyzer tslint
    

    Via the PNPM CLI:

    pnpm remove codelyzer tslint
    

    Via the Yarn CLI:

    yarn remove codelyzer tslint
    

    A more durable alternative is to add a preinstall script that permanently erases Codelyzer and TSLint even when generators attempt to restore them.

    Via the NPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(npm uninstall codelyzer || echo ✅ Codelyzer is already removed.) && (npm uninstall tslint || echo ✅ TSLint is already removed.)';"
    npm install
    

    Via the PNPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(pnpm remove codelyzer || echo ✅ Codelyzer is already removed.) && (pnpm remove tslint || echo ✅ TSLint is already removed.)';"
    pnpm install
    

    Via the Yarn CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(yarn remove codelyzer || echo ✅ Codelyzer is already removed.) && (yarn remove tslint || echo ✅ TSLint is already removed.)';"
    yarn install
    
  4. Turn on Angular strict mode.
    We recommend keeping both TypeScript and Angular in strict mode. Strict mode applies to both application and library projects.

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:application'].strict = true;"
    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:library'].strict = true;"
    
  5. Select the unit test runner.
    Nx ships with first-class support for both Jest and Karma in Angular application and library projects.

    Choose Jest:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:application'].unitTestRunner = 'jest';"
    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:library'].unitTestRunner = 'jest';"
    

    Choose Karma:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:application'].unitTestRunner = 'karma';"
    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:library'].unitTestRunner = 'karma';"
    
  6. Select the end-to-end test runner.
    For end-to-end tests in Angular applications, Nx supports Cypress and Protractor out of the box.

    Choose Cypress:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular'].application.e2eTestRunner = 'cypress';"
    

    Choose Protractor:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular'].application.e2eTestRunner = 'protractor';"
    
  7. Merge the schematics defaults.
    When you invoke create-nx-workspace with --preset=angular --linter=eslint in Nx 11.0.18, the workspace gets duplicate entries for the default Angular application and library schematics inside angular.json. Those duplicates break the configuration. This needs to be fixed before anything else.

    Merge the Angular application schematic defaults:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:application'].linter = this.schematics['@nrwl/angular'].application.linter; delete this.schematics['@nrwl/angular'].application;"
    

    Merge the Angular library schematic defaults:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:library'].linter = this.schematics['@nrwl/angular'].library.linter; delete this.schematics['@nrwl/angular'].library;"
    
  8. Recreate the application and end-to-end test projects when applying non-default runners.
    If you picked Karma and Protractor instead of Jest and Cypress, the original application and end-to-end test projects must be removed and regenerated.

    Remove the end-to-end test and application projects:

    nx generate @nrwl/workspace:remove booking-app-e2e
    nx generate @nrwl/workspace:remove booking-app
    

    Generate both projects again:

    nx generate @nrwl/angular:application --name=booking-app --prefix=booking --no-interactive
    

    Remove any root-level Jest configuration files:

    rm jest.config.js
    rm jest.preset.js
    
  9. Assign project tags.

    Add tags to the freshly generated application and end-to-end test projects.

    npx json -I -f nx.json -e "this.projects['booking-app'].tags = ['type:app','scope:booking'];"
    npx json -I -f nx.json -e "this.projects['booking-app-e2e'].tags = ['type:e2e','scope:booking'];"
    
  10. Apply strict Angular build budgets.
    Nx 11.0.18 keeps build budgets unchanged even when Angular strict mode is on. We therefore mirror the budgets used by Angular CLI 11 in strict mode.

    The main bundle warns at 500 KB and errors at 1 MB; component styles warn at 2 KB and error at 4 KB.

    npx json -I -f angular.json -e "this.projects['booking-app'].architect.build.configurations.production.budgets = [{ type: 'initial', maximumWarning: '500kb', maximumError: '1mb' }, { type: 'anyComponentStyle', maximumWarning: '2kb', maximumError: '4kb' }];"
    
  11. Create a workspace library.
    We add an Angular library to confirm that the configuration holds for libraries as well.

    nx generate @nrwl/angular:library --name=feature-flight-search --directory=booking --prefix=booking --tags="type:feature,scope:booking" --buildable --enable-ivy --no-interactive
    

    The library is set up as buildable and Ivy-based, though not publishable, so that we benefit from Nx 11's incremental builds and computation caching.

  12. Remove Codelyzer again.
    Angular CLI 11 injects Codelyzer by default during workspace or application generation, so we need to uninstall it once more.
    Via the NPM CLI:

    npm uninstall codelyzer
    

    Via the PNPM CLI:

    pnpm remove codelyzer
    

    Via the Yarn CLI:

    yarn remove codelyzer
    
  13. Confirm linting works.
    Execute the lint target against all projects to confirm ESLint with angular-eslint is operational.

    nx run-many --target=lint --all
    

The result of this flow is an Nx workspace with one Angular application and one Angular library. Using the angular preset puts us on version 1 of the Nx workspace format, identical to Angular CLI's. The vocabulary stays the same: builders, schematics, and architect targets.

Inspecting angular.json confirms that each lint target is wired to the @nrwl/linter:eslint executor.

The root .eslintrc.json references the @nrwl/nx/typescript ESLint plugin. The per-project ESLint configuration files for the application and the library should include the @nrwl/nx/angular, @nrwl/nx/angular-template, and @angular-eslint/template/process-inline-templates plugins.

Migrating an existing Nx 10 Angular workspace that uses ESLint

Workspaces that rely on ESLint already are migrated to angular-eslint as part of jumping to Nx 11.

  1. Generate an Nx 10 workspace with the angular preset.
    For this walkthrough, we create a fresh Nx Angular workspace with one application.

    Via the NPM CLI:

    npm init nx-workspace@10 nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --strict --no-nx-cloud --style=css --package-manager=npm --linter=eslint
    

    Via the PNPM CLI:

    pnpm init nx-workspace@10 nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --strict --no-nx-cloud --style=css --package-manager=pnpm --linter=eslint
    

    Keep in mind that PNPM is supported only from Nx 11 onward.

    Via the Yarn CLI:

    yarn global add create-nx-workspace@10
    create-nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --strict --no-nx-cloud --style=css --package-manager=yarn --linter=eslint
    
  2. Remove Codelyzer and TSLint.
    Codelyzer and TSLint ship with Nx by default. Since TSLint has reached its end of life, it is time to say goodbye. Uninstall codelyzer and tslint.

    Via the NPM CLI:

    npm uninstall codelyzer tslint
    

    Via the Yarn CLI:

    yarn remove codelyzer tslint
    

    Alternatively, add a preinstall script that strips out Codelyzer and TSLint on every install even when generators re-insert them.

    Via the NPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(npm uninstall codelyzer || echo ✅ Codelyzer is already removed.) && (npm uninstall tslint || echo ✅ TSLint is already removed.)';"
    npm install
    

    Via the PNPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(pnpm remove codelyzer || echo ✅ Codelyzer is already removed.) && (pnpm remove tslint || echo ✅ TSLint is already removed.)';"
    pnpm install
    

    Via the Yarn CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(yarn remove codelyzer || echo ✅ Codelyzer is already removed.) && (yarn remove tslint || echo ✅ TSLint is already removed.)';"
    yarn install
    
  3. Merge the schematics defaults.
    In Nx 11.0.18, using --preset=angular --linter=eslint with create-nx-workspace again yields duplicate Angular application and library schematic defaults in angular.json. Duplicates make the configuration unusable. Fix this first.

    Merge the Angular application schematic defaults:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:application'].linter = this.schematics['@nrwl/angular'].application.linter; delete this.schematics['@nrwl/angular'].application;"
    

    Merge the Angular library schematic defaults:

    npx json -I -f angular.json -e "this.schematics['@nrwl/angular:library'].linter = this.schematics['@nrwl/angular'].library.linter; delete this.schematics['@nrwl/angular'].library;"
    
  4. Add a workspace library.
    We include an Angular library so the example feels closer to a real project.

    nx generate @nrwl/angular:library feature-flight-search --directory=booking --prefix=booking --tags="type:feature,scope:booking" --buildable --no-interactive
    
  5. Uninstall Codelyzer.
    When Angular CLI 11 creates a workspace or application, it also adds Codelyzer by default, so remove it again.

    Via the NPM CLI:

    npm uninstall codelyzer
    

    Via the Yarn CLI:

    yarn remove codelyzer
    
  6. Upgrade to Nx 11.
    The update to Nx 11 brings angular-eslint automatically for those workspaces already using ESLint.

    Via the NPM CLI:

    nx migrate @nrwl/workspace
    npm install
    
    # Good point in time to review migrations.json and make a commit before applying selected migrations
    nx migrate --run-migrations=migrations.json
    npm install
    rm migrations.json
    

    Via the PNPM CLI:

    nx migrate @nrwl/workspace
    pnpm install
    
    # Good point in time to review migrations.json and make a commit before applying selected migrations
    nx migrate --run-migrations=migrations.json
    pnpm install
    rm migrations.json
    

    Via the Yarn CLI:

    nx migrate @nrwl/workspace
    yarn install
    
    # Good point in time to review migrations.json and make a commit before applying selected migrations
    nx migrate --run-migrations=migrations.json
    yarn install
    rm migrations.json
    
  7. Verify linting.
    Run the lint target for every project to confirm angular-eslint is working.

    nx run-many --target=lint --all
    
  8. Bump angular-eslint.
    Nx 11.0.18 installs angular-eslint 0.8.0-beta.1; we update to the latest release now.

    Via the NPM CLI:

    npm install --save-dev @angular-eslint/eslint-plugin@latest @angular-eslint/eslint-plugin-template@latest @angular-eslint/template-parser@latest
    

    Via the PNPM CLI:

    pnpm add --save-dev @angular-eslint/eslint-plugin@latest @angular-eslint/eslint-plugin-template@latest @angular-eslint/template-parser@latest
    

    Via the Yarn CLI:

    yarn add @angular-eslint/eslint-plugin@latest @angular-eslint/eslint-plugin-template@latest @angular-eslint/template-parser@latest
    
  9. Re-verify linting.
    Execute the lint target for all projects again to ensure angular-eslint operates correctly at the newer version.

    nx run-many --target=lint --all
    

Migrating an existing Nx 10 Angular workspace that uses TSLint

As of Nx 11.0.18, there are no schematics that take an Nx Angular workspace using TSLint and automatically convert it to ESLint with angular-eslint.

The workaround is to apply the TSLint-to-ESLint migration schematics provided by angular-eslint itself, then handle several adjustments by hand so the final state matches what a fully migrated Nx Angular workspace would look like.

This example assumes the default test runners for the angular preset, which are Cypress and Jest. With Protractor and Karma, only the end-to-end test project configuration changes. If you want to compare ESLint setups, generate a brand-new Nx workspace with Karma, Protractor, and ESLint as discussed earlier in this article.

Be aware that the angular preset in this guide relies on angular.json. The angular-eslint migration schematics do not understand Nx workspaces that use workspace.json.

  1. Create an Nx 10 workspace with the angular preset.
    We begin with a new Nx 10 workspace for the sake of demonstration. If a workspace already exists, adapt these steps to its specifics.

    Via the NPM CLI:

    npm init nx-workspace@10 nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --strict --no-nx-cloud --style=css --package-manager=npm --linter=tslint
    

    Via the PNPM CLI:

    pnpx create-nx-workspace@10 nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --strict --no-nx-cloud --style=css --package-manager=pnpm --linter=tslint
    

    PNPM is only available from Nx 11 onward.

    Via the Yarn CLI:

    yarn global add create-nx-workspace@10
    create-nx-workspace nrwl-airlines --npm-scope=nrwl-airlines --preset=angular --app-name=booking-app --strict --no-nx-cloud --style=css --package-manager=yarn --linter=tslint
    
  2. Add an Angular workspace library.
    This library project exists purely for the example. If you already have a workspace, this step is unnecessary.

    nx generate @nrwl/angular:library --name=feature-flight-search --directory=booking --prefix=booking --tags="type:feature,scope:booking" --buildable --no-interactive
    
  3. Upgrade to Nx 11.
    This step is technically optional. All of the actions that follow work identically on Nx 10.

    Via the NPM CLI:

    nx migrate @nrwl/workspace
    npm install
    
    # Good point in time to review migrations.json and make a commit before applying selected migrations
    nx migrate --run-migrations=migrations.json
    npm install
    rm migrations.json
    

    Via the PNPM CLI:

    nx migrate @nrwl/workspace
    pnpm install
    
    # Good point in time to review migrations.json and make a commit before applying selected migrations
    nx migrate --run-migrations=migrations.json
    pnpm install
    rm migrations.json
    

    Via the Yarn CLI:

    nx migrate @nrwl/workspace
    yarn install
    
    # Good point in time to review migrations.json and make a commit before applying selected migrations
    nx migrate --run-migrations=migrations.json
    yarn install
    rm migrations.json
    
  4. Shift to angular-eslint.
    First, rename tsconfig.base.json to tsconfig.json temporarily. The angular-eslint migration schematics are not built for solution-style TypeScript setups, which Nx has used since version 10.0.

    mv tsconfig.base.json tsconfig.json
    

    Now execute the angular-eslint schematics to install the required dev dependencies, such as eslint-plugin-*, @angular-eslint/*, and @typescript-eslint/*.

    Via the NPM CLI:

    npm install --save-dev @angular-eslint/schematics
    nx generate @angular-eslint/schematics:ng-add
    

    Via the PNPM CLI:

    pnpm add --save-dev @angular-eslint/schematics
    nx generate @angular-eslint/schematics:ng-add
    

    Via the Yarn CLI:

    yarn add @angular-eslint/schematics
    nx generate @angular-eslint/schematics:ng-add
    

    This may downgrade the eslint version that Nx installed. If so, restore the version Nx brought in. For instance:

    Via the NPM CLI:

    npm install --save-dev eslint@7.10.0
    

    Via the PNPM CLI:

    pnpm add --save-dev eslint@7.10.0
    

    Via the Yarn CLI:

    yarn add eslint@7.10.0
    

    After that, run angular-eslint's converter for every Angular application and library project in the workspace.

    Depending on your TSLint rules, you may see warnings such as this one:

    WARNING: Within "tslint.json", the following 1 rule(s) did not have known converters in https://github.com/typescript-eslint/tslint-to-eslint-config
    
      - nx-enforce-module-boundaries
    
    You will need to decide on how to handle the above manually, but everything else has been handled for you automatically.
    

    Regarding the nx-enforce-module-boundaries rule, the only rule that warns us in our example workspace, there is no cause for alarm because the root TSLint file stays until the very last step. That rule serves the nx workspace-lint command.

    In the ESLint world, the counterpart is @nrwl/nx/enforce-module-boundaries, which we add to the root ESLint config during a later step.

    You can either run the generator by hand for each project:

    # Migrate booking-app rules to angular-eslint
    nx generate @angular-eslint/schematics:convert-tslint-to-eslint booking-app
    
    # Migrate booking-app-e2e rules to angular-eslint
    nx generate @angular-eslint/schematics:convert-tslint-to-eslint booking-app-e2e
    
    # Migrate booking-feature-flight-search rules to angular-eslint
    nx generate @angular-eslint/schematics:convert-tslint-to-eslint booking-feature-flight-search
    

    or cycle through the project names from angular.json and invoke the generator inside a loop.

    A PowerShell version:

    foreach ($project in (Get-Content angular.json | ConvertFrom-Json -AsHashtable).projects.GetEnumerator()) { nx generate @angular-eslint/schematics:convert-tslint-to-eslint $project.Name }
    

    A Bash version:

    for project in $(cat angular.json | npx json projects | npx json -M -a key); do nx generate @angular-eslint/schematics:convert-tslint-to-eslint $project; done
    

    Finally, put tsconfig.base.json back in place.

    mv tsconfig.json tsconfig.base.json
    
  5. Adapt angular-eslint for the Nx workspace.

    Start by removing unneeded dev dependencies.

    Via the NPM CLI:

    npm uninstall @angular-eslint/builder @angular-eslint/schematics
    

    Via the PNPM CLI:

    pnpm remove @angular-eslint/builder @angular-eslint/schematics
    

    Via the Yarn CLI:

    yarn remove @angular-eslint/builder @angular-eslint/schematics
    

    Then install the necessary dev dependencies.

    Via the NPM CLI:

    npm install --save-dev @nrwl/eslint-plugin-nx eslint-config-prettier eslint-plugin-cypress
    

    Via the PNPM CLI:

    pnpm add --save-dev @nrwl/eslint-plugin-nx eslint-config-prettier eslint-plugin-cypress
    

    Via the Yarn CLI:

    yarn add --dev @nrwl/eslint-plugin-nx eslint-config-prettier eslint-plugin-cypress
    

    Now set up the root ESLint configuration.

    # Ignore all files not matched in overrides
    npx json -I -f .eslintrc.json -e "this.ignorePatterns = ['**/*'];"
    
    # Support ESLint plugins from `@nrwl/eslint-plugin-nx`
    npx json -I -f .eslintrc.json -e "this.plugins = ['@nrwl/nx'];"
    
    # Include tsx files
    # Can be left out from an Angular-only workspace
    npx json -I -f .eslintrc.json -e "this.overrides[0].files = ['*.ts', '*.tsx'];"
    
    # Match all TypeScript project configuration files
    npx json -I -f .eslintrc.json -e "this.overrides[0].parserOptions.project = './tsconfig.*?.json';"
    
    # This setting is not used by the Nrwl Linter
    npx json -I -f .eslintrc.json -e "delete this.overrides[0].parserOptions.createDefaultProgram;"
    
    # Replace angular-eslint plugins with the Nx TypeScript ESLint plugin as it uses them internally
    npx json -I -f .eslintrc.json -e "this.overrides[0].extends = ['plugin:@nrwl/nx/typescript'];"
    
    # Remove component template rule as this is defined in project-specific ESLint configurations
    npx json -I -f .eslintrc.json -e "this.overrides = this.overrides.slice(0, 1);"
    
    # Use Nx JavaScript ESLint plugin for js and jsx files
    # Can be left out from an Angular-only workspace
    npx json -I -f .eslintrc.json -e "this.overrides = [...this.overrides, { files: ['*.js', '*.jsx'], extends: ['plugin:@nrwl/nx/javascript'], rules: {} }];"
    
    # Remove angular-eslint rules that are added to project-specific ESLint configurations
    npx json -I -f .eslintrc.json -e "delete this.overrides[0].rules['@angular-eslint/component-selector'];"
    npx json -I -f .eslintrc.json -e "delete this.overrides[0].rules['@angular-eslint/directive-selector'];"
    

    Last for the root config is ensuring our workspace lint rules (and any others angular-eslint warned about) are merged in.

    # This is where we configure the workspace lint rules
    # Refer to the root TSLint configuration
    npx json -I -f .eslintrc.json -e "this.overrides = [{ files: ['*.ts', '*.tsx', '*.js', '*.jsx'], rules: { '@nrwl/nx/enforce-module-boundaries': ['error', { enforceBuildableLibDependency: true, allow: [], depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }] }] } }, ...this.overrides];"
    

    It is time to wire up the ESLint config for each project. First, the booking-app project.

    # Add Nx Angular ESLint plugin and the ESLint inline component template processor
    npx json -I -f apps/booking-app/.eslintrc.json -e "this.overrides[0].extends = ['plugin:@nrwl/nx/angular', 'plugin:@angular-eslint/template/process-inline-templates'];"
    
    # Match all TypeScript project configuration files
    npx json -I -f apps/booking-app/.eslintrc.json -e "this.overrides[0].parserOptions.project = [this.overrides[0].parserOptions.project[0].replace('/tsconfig.app.json', '/tsconfig.*?.json')];"
    
    # This setting is not used by the Nrwl Linter
    npx json -I -f apps/booking-app/.eslintrc.json -e "delete this.overrides[0].parserOptions.createDefaultProgram;"
    
    # Use the ESLint component template processor and recommended component template rules from angular-eslint
    npx json -I -f apps/booking-app/.eslintrc.json -e "this.overrides[1].extends = ['plugin:@nrwl/nx/angular-template', 'plugin:@angular-eslint/template/recommended'];"
    

    Then we handle booking-feature-flight-search. The modifications are identical to those for booking-app, except that this config sits three levels deep, so the relative path to the root ESLint config must be corrected.

    # Correct path to root ESLint configuration
    npx json -I -f libs/booking/feature-flight-search/.eslintrc.json -e "this.extends = '../' + this.extends;"
    
    # Add Nx Angular ESLint plugin and the ESLint inline component template processor
    npx json -I -f libs/booking/feature-flight-search/.eslintrc.json -e "this.overrides[0].extends = ['plugin:@nrwl/nx/angular', 'plugin:@angular-eslint/template/process-inline-templates'];"
    
    # Match all TypeScript project configuration files
    npx json -I -f libs/booking/feature-flight-search/.eslintrc.json -e "this.overrides[0].parserOptions.project = [this.overrides[0].parserOptions.project[0].replace('/tsconfig.lib.json', '/tsconfig.*?.json')];"
    
    # This setting is not used by the Nrwl Linter
    npx json -I -f libs/booking/feature-flight-search/.eslintrc.json -e "delete this.overrides[0].parserOptions.createDefaultProgram;"
    
    # Use the ESLint component template processor and recommended component template rules from angular-eslint
    npx json -I -f libs/booking/feature-flight-search/.eslintrc.json -e "this.overrides[1].extends = ['plugin:@nrwl/nx/angular-template', 'plugin:@angular-eslint/template/recommended'];"
    

    Finally, set up ESLint for booking-app-e2e.

    # Use rules recommended by Cypress
    npx json -I -f apps/booking-app-e2e/.eslintrc.json -e "this.extends = ['plugin:cypress/recommended', this.extends];"
    
    # Delete rule for component templates
    npx json -I -f apps/booking-app-e2e/.eslintrc.json -e "this.overrides = this.overrides.slice(0, 1);"
    
    # Add rules specifically for the Cypress plugin loader
    npx json -I -f apps/booking-app-e2e/.eslintrc.json -e "this.overrides = [{ files: ['src/plugins/index.js'], rules: { '@typescript-eslint/no-var-requires': 'off', 'no-undef': 'off' } }, ...this.overrides];"
    
    # Match all TypeScript project configuration files
    npx json -I -f apps/booking-app-e2e/.eslintrc.json -e "this.overrides[1].parserOptions.project = [this.overrides[1].parserOptions.project[0].replace('/tsconfig.app.json', '/tsconfig.*?.json')];"
    
    # This setting is not used by the Nrwl Linter
    npx json -I -f apps/booking-app-e2e/.eslintrc.json -e "delete this.overrides[1].parserOptions.createDefaultProgram;"
    
    # Remove Angular declarable rules
    npx json -I -f apps/booking-app-e2e/.eslintrc.json -e "delete this.overrides[1].rules['@angular-eslint/component-selector'];"
    npx json -I -f apps/booking-app-e2e/.eslintrc.json -e "delete this.overrides[1].rules['@angular-eslint/directive-selector'];"
    

    Open apps/booking-app-e2e/src/support/commands.ts and insert the comment below right before the line beginning with declare namespace Cypress {:

    // eslint-disable-next-line @typescript-eslint/no-namespace
    

    In that same file, drop this comment before the line containing interface Chainable<Subject> {:

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    
  6. Swap in the Nrwl Linter builder.
    The last configuration change is replacing @angular-eslint/builder:lint with @nrwl/linter:eslint in the workspace config.

    # Use Nrwl Linter
    npx json -I -f angular.json -e "this.projects['booking-app'].architect.lint.builder = '@nrwl/linter:eslint';"
    npx json -I -f angular.json -e "this.projects['booking-feature-flight-search'].architect.lint.builder = '@nrwl/linter:eslint';"
    npx json -I -f angular.json -e "this.projects['booking-app-e2e'].architect.lint.builder = '@nrwl/linter:eslint';"
    
    # Only lint js and ts files in the end-to-end test project
    npx json -I -f angular.json -e "this.projects['booking-app-e2e'].architect.lint.options.lintFilePatterns = [this.projects['booking-app-e2e'].architect.lint.options.lintFilePatterns[0].replace('*.ts', '*.{js,ts}')];"
    
  7. Get rid of Codelyzer and TSLint.

    Via the NPM CLI:

    npm uninstall codelyzer tslint
    rm tslint.json
    

    Via the Yarn CLI:

    yarn remove codelyzer tslint
    rm tslint.json
    

    Or use a preinstall script so that Codelyzer and TSLint are removed repeatedly, even when generators try to restore them.

    Via the NPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(npm uninstall codelyzer || echo ✅ Codelyzer is already removed.) && (npm uninstall tslint || echo ✅ TSLint is already removed.)';"
    npm install
    

    Via the PNPM CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(pnpm remove codelyzer || echo ✅ Codelyzer is already removed.) && (pnpm remove tslint || echo ✅ TSLint is already removed.)';"
    pnpm install
    

    Via the Yarn CLI:

    npx json -I -f package.json -e "this.scripts.preinstall = '(yarn remove codelyzer || echo ✅ Codelyzer is already removed.) && (yarn remove tslint || echo ✅ TSLint is already removed.)';"
    yarn install
    
  8. Verify linting.
    Invoke the lint target on all projects to confirm everything works with ESLint and angular-eslint.

    nx run-many --target=lint --all
    

Conclusion

The empty preset for Nx stands out because it lands on the new workspace.json version 2 schema, which uses executors, generators, and targets. This setup is flexible and plays nicely with angular-eslint.

Alternatively, the angular preset produces a workspace that continues to use angular.json.

An Nx 10 workspace already on ESLint makes the journey to Nx 11 painless: angular-eslint is installed and configured for every existing ESLint-based project during the upgrade.

An Nx 10 workspace on TSLint upgrades to Nx 11 without friction, but there is no automated TSLint-to-angular-eslint migration yet in Nx 11.0.18.

That said, Angular CLI workspaces do have migration paths. Those migrations are a reliable springboard for installing angular-eslint and generating the required ESLint configs and plugins.

To manually bring angular-eslint into an Nx workspace, adjust the ESLint configs so they mirror what a new Nx workspace would have and switch from the angular-eslint builder to the Nrwl Linter.

Regardless of your current stack, Codelyzer and TSLint can be retired today in favor of angular-eslint.

Some Codelyzer rules for Angular-specific TSLint checks have no matching angular-eslint rule yet. At publication time, these rules remain unimplemented:

  • angular-whitespace
  • contextual-decorator
  • import-destructuring-spacing
  • no-unused-css
  • prefer-inline-decorator
  • template-accessibility-alt-text
  • template-accessibility-label-for
  • template-accessibility-table-scope
  • template-click-events-have-key-events
  • template-conditional-complexity
  • template-no-any

Why the rush to leave TSLint behind? On December 1st, 2020, TSLint officially reached its end of life. No pull requests or issues will ever be merged or answered again. That means TSLint 6.1.3 — the absolute final release — can be broken at any moment by a new version of Angular, TypeScript, Node.js, or any other dependency. TSLint was already deprecated two years ago.

Recognitions

Credit is due to James Henry for his work on angular-eslint, as well as to Nrwl and James Henry for bringing angular-eslint support into Nx.