Skip to content
Go back

ESLint Rules To Your Angular App More Better

Published:  at  08:00 PM

I love using ESLint to keep me from doing dumb stuff. I am sometimes a doer of dumb things.

Also, there are some things that used to not be dumb, but have become dumb over time, as the languages, frameworks, or even our approaches “evolve”.

I am a firm believer that the Angular decorator “@Injectable({providedIn: ‘root})` should just probably never be used in the context of an application (there is some argument to be made for a library, but even then…).

I disallow it with an ESLint rule.

And for those that use the @ngrx/store library, at this point your components should be almost exclusively be using the store’s selectSignal method instead of select. Both take a selector function, but the older select returns that as an observable (from which you have to subscribe, unsubscribe, etc.) whereas the selectSignal method takes the same selector function, but returns a nice clean, refreshing signal of the same data.

I create an ESLint rule for that, as well.

For example, the eslint.config.js in my Angular project (Angular 20.1.0) looks like this:

// @ts-check
const eslint = require("@eslint/js");
const tseslint = require("typescript-eslint");
const angular = require("angular-eslint");

module.exports = tseslint.config(
  {
    files: ["**/*.ts"],
    extends: [
      eslint.configs.recommended,
      ...tseslint.configs.recommended,
      ...tseslint.configs.stylistic,
      ...angular.configs.tsRecommended,
    ],
    processor: angular.processInlineTemplates,
    rules: {
      "no-restricted-syntax": [
        "error",
        {
          selector:
            "Decorator[expression.callee.name='Injectable'] > CallExpression[arguments.length=1] > ObjectExpression > Property[key.name='providedIn'][value.value='root']",
          message:
            "Are you sure you don't want to just create a provider for this?",
        },
        {
          selector:
            "CallExpression[callee.type='MemberExpression'][callee.property.name='select']",
          message:
            "Use the `selectSignal` method instead of `select` on Store instances. Found .select() call - consider using .selectSignal() for signals.",
        },
      ],
      "@typescript-eslint/consistent-type-definitions": "off",
      "@angular-eslint/directive-selector": [
        "error",
        {
          type: "attribute",
          prefix: "app",
          style: "camelCase",
        },
      ],
      "@angular-eslint/component-selector": [
        "error",
        {
          type: "element",
          prefix: "app",
          style: "kebab-case",
        },
      ],
    },
  },
  {
    files: ["**/*.html"],
    extends: [
      ...angular.configs.templateRecommended,
      ...angular.configs.templateAccessibility,
    ],
    rules: {},
  },
);

The rules for no-restricted-syntax disallow both of these things.


Suggest Changes
Share this post on:

Next Post
Using Dan Abramov Using AI To Solve My Problems