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.