Skip to content

Angular utility for ensuring exhaustive checks on TypeScript discriminated unions, enhancing type safety and reliability.

License

Notifications You must be signed in to change notification settings

soc221b/ngx-exhaustive-check

Repository files navigation

NgxExhaustiveCheck

Angular utility for ensuring exhaustive checks on TypeScript discriminated unions, enhancing type safety and reliability.

Installation

$ npm install ngx-exhaustive-check

Usage

Before

Without an exhaustive check, the code may compile successfully, but this can lead to runtime errors:

import { Component } from '@angular/core';

enum Answer {
  Yes,
  No,
+ Maybe,
}

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    @switch (answer) {
      @case (Answer.Yes) {}
      @case (Answer.No) {}
      @default {}
    }
  `,
})
export class AppComponent {
  answer: Answer = Answer.Yes;
  Answer = Answer;
}

After

With an exhaustive check, the compilation will fail, making your code more reliable:

import { Component } from '@angular/core';
+ import { ExhaustiveCheckPipe } from 'ngx-exhaustive-check';

enum Answer {
  Yes,
  No,
+ Maybe,
}

@Component({
  selector: 'app-root',
  standalone: true,
+ imports: [ExhaustiveCheckPipe],
  template: `
    @switch (answer) {
      @case (Answer.Yes) {}
      @case (Answer.No) {}
      @default {
+       {{ answer | exhaustiveCheck }}
   <!--    ^^^^^^ Argument of type 'Answer' is not assignable to parameter of type 'never'. -->
      }
    }
  `,
})
export class AppComponent {
  answer: Answer = Answer.Yes;
  Answer = Answer;
}

Advanced usage

Sometimes, if you just want to ignore some cases, you can do this:

enum Answer {
  Yes,
  No,
+ NoOp1,
+ NoOp2,
}
@switch (answer) {
  @case (Answer.Yes) {}
  @case (Answer.No) {}
+ @case (Answer.NoOp1) {}
+ @case (Answer.NoOp2) {}
  @default {
    {{ answer | exhaustiveCheck }}
  }
}

With ngx-exhaustive-check, you can achieve this by passing the satisfies parameter as well:

@switch (answer) {
  @case (Answer.Yes) {}
  @case (Answer.No) {}
  @default {
-   {{ answer | exhaustiveCheck }}
+   {{ answer | exhaustiveCheck: [Answer.NoOp1, Answer.NoOp2] }}
  }
}

This is useful when you want to apply the same action to these cases.