← All articles

I Released an ESLint Plugin with Useful Rules for Ionic Angular Projects πŸŽ‰

@rdlabo/eslint-plugin-rules bundles ESLint rules for Ionic Angular: migrate constructor DI to inject with auto-fix, enforce inject imports, and block inline overlay elements.

Published
I Released an ESLint Plugin with Useful Rules for Ionic Angular Projects πŸŽ‰ cover image

Overview

I released @rdlabo/eslint-plugin-rules πŸŽ‰
I build apps with Ionic and Angular, and I packaged the ESLint rules I needed along the way into a plugin. Some of the features honestly made my work dramatically more efficient. On a large application, migration work that would normally take two or three days finished in an instant. Hooray!

Usage

  • Assumes @angular/core >= 15. It should work on 14, but not on 13.
  • You need the @angular-eslint packages applied to your project. If you have not done that yet, install them using this guide.
  • If you use prettier, keep it up to date. An old prettier version throws an error when you inject an object with a generic type parameter.

Installation

% npm install @rdlabo/eslint-plugin-rules --save-dev

Configuration

Configure .eslintrc* as follows. This shows my recommended setup, but of course you can skip any rules you do not want.

json
  {
    ...
+   "plugins": ["@rdlabo/rules"],
    "overrides": [
      {
        "files": [
          "*.ts"
        ],
      ...
        "rules": {
+         "@rdlabo/rules/deny-constructor-di": "error",
+         "@rdlabo/rules/import-inject-object": "error",
        }
      },
      {
        "files": [
          "*.html"
        ],
        "rules": {
+         "@rdlabo/rules/deny-element": [
+           "error",
+           {
+             "elements": [
+               "ion-modal",
+               "ion-popover",
+               "ion-toast",
+               "ion-alert",
+               "ion-loading",
+               "ion-picker",
+               "ion-action-sheet"
+             ]
+           }
+         ]
          ]
        }
      }
    ]
  }

Rule reference

@rdlabo/rules/deny-constructor-di

This rule helps you migrate to the inject function introduced in Angular 14. Use it when moving away from DI in constructor. Both styles work today, but after reading the article below you will want to migrate!

https://zenn.dev/lacolaco/articles/why-inject-function-winshttps://zenn.dev/lacolaco/articles/why-inject-function-wins

Before migration: DI in constructor triggers an error.

@Component({
  selector: 'app-confirm',
  templateUrl: './confirm.page.html',
  styleUrls: ['./confirm.page.scss'],
})
export class SigninPage {
  constructor(public platform: Platform) {} // Error: Do not use constructor DI.
}

After migration: use the inject function.

@Component({
  selector: 'app-confirm',
  templateUrl: './confirm.page.html',
  styleUrls: ['./confirm.page.scss'],
})
export class SigninPage {
  public readonly platform = inject(Platform); // OK

  constructor() {}
}

This rule supports auto-fix, so npm run lint -- --fix completes the migration automatically. For the example above, it generates a diff like this.

ts
    @Component({
      selector: 'app-confirm',
      templateUrl: './confirm.page.html',
      styleUrls: ['./confirm.page.scss'],
    })
    export class SigninPage {
-     constructor(public platform: Platform) {}
+     public readonly platform = inject(Platform); // OK
+
+     constructor() {}
    }
ts
    @Component({
      selector: 'app-confirm',
      templateUrl: './confirm.page.html',
      styleUrls: ['./confirm.page.scss'],
    })
    export class SigninPage {
-     constructor(
-       public platform: Platform,
-       private store: Store<IApp>,
-       private navCtrl: NavController,
-       public helper: HelperService,
-     ) {}      
+     public readonly platform = inject(Platform);
+     private readonly store = inject(Store<IApp>);
+     private readonly navCtrl = inject(NavController);
+     public readonly helper = inject(HelperService);
+     
+     constructor() {}
    }

On a large project that means more than 100 files and thousands of DI sitesβ€”fixing that by hand is not realistic. You could die trying. Let automation do the work!

@rdlabo/rules/import-inject-object

If you use the inject function without importing it, you get an error. I split this out because generating the inject import in the auto-fix for @rdlabo/rules/deny-constructor-di was hard. npm run lint -- --fix adds the import automatically.

ts
+   import { inject } from '@angular/core';

    @Component({
      selector: 'app-confirm',
      templateUrl: './confirm.page.html',
      styleUrls: ['./confirm.page.scss'],
    })
    export class SigninPage {
      public readonly platform = inject(Platform);
    }
ts
-   import { Component } from '@angular/core';
+   import { Component, inject } from '@angular/core';

    @Component({
      selector: 'app-confirm',
      templateUrl: './confirm.page.html',
      styleUrls: ['./confirm.page.scss'],
    })
    export class SigninPage {
      public readonly platform = inject(Platform);
    }

@rdlabo/rules/deny-element

Since Ionic 6, inline Modals and ActionSheets are supported. On projects I work on, we standardize on creating them through Controllers, so I added this rule to avoid accidentally creating them inline. You can also use it to ban other specific HTML elements.

{
  "rules": {
    "@rdlabo/rules/deny-element": [
      "error",
      {
        "elements": ["ion-modal"]
      }
    ]
  }
}
<ion-modal></ion-modal><!-- This causes an error -->

Summary

In day-to-day development, "try hard" and "be careful" inevitably wear you down. Turning expectations into rules and automating them reduces the burden on developers. Please give it a try. And if it saves you real time, buy me a drink sometime (lol)