3

In Angular source there is a Validators class that starts as follows:

export class Validators {
  
  static min(min: number): ValidatorFn {
    return minValidator(min);
  }

  // ...

}

Is it possible to create another class on my application with the same name?

export class Validators {
  
  static dark(color: string): ValidatorFn {
    return darkValidator(color);
  }

  // ...

}

I would like to use both min and dark validators using:

Validators.min(2);
Validators.dark('#FFF');

In C#, for example, I could use partial class.

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481

1 Answers1

3

JavaScript doesn't have partial class and therefore neither does TypeScript, as it would have an effect at runtime and is therefore not aligned with TypeScript's design goals (see TypeScript Design Goal #8, "avoid adding expression-level syntax" and TypeScript Design Non-Goal #6, "provide additional runtime functionality"). There was a suggestion for this at microsoft/TypeScript#563 but it was declined as out of scope: such a feature would need to exist in JavaScript before TypeScript would support it.

Instead of partial class, you could use monkey patching to add behavior to the Validators class from Angular. Right now you only seem to be asking about static methods. Since static methods are essentially the same as a property on the Validators class constructor itself, you can just add a dark property that does what you want. In JavaScript that would look like this:

import { Validators } from '@angular/forms';        
Validators.dark = function(color) {
  return darkValidator(color);
};
Validators.min(2); // okay
Validators.dark('#FFF'); // okay

In order for this to work in TypeScript, you need to do some declaration merging to convince the compiler that Validators has a static dark method. There is an open suggestion at microsoft/TypeScript#2957 to allow some easy way of doing this for the static side of classes (the instance side is a bit easier), but for now the available solution is to declare a namespace with an exported variable named dark of the right type:

import { Validators, ValidatorFn } from '@angular/forms';
    
declare module '@angular/forms' {
  export namespace Validators {
    export let dark: (this: typeof Validators, color: string) => ValidatorFn;
  }
}

Validators.dark = function(color: string): ValidatorFn {
  return darkValidator(color);
};

Validators.min(2); // okay
Validators.dark('#FFF'); // okay

The declare module is module augmentation that lets you add typings to the imported '@angular/forms' module. We've told the compiler that that the exported value named Validators is a namespace in addition to being a class constructor. This lets you assign and use Validators.dark() with no compiler warning.

StackBlitz link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Great answer. Really clear and it helped a lot. Thanks. – Miguel Moura Sep 10 '21 at 11:27
  • Is it possible to use this code having the `declare module ... ` and the `Validators.dark = function` in a TS file and then using `Validators.dark('#FFF')` on tother TS files? I am asking because I am having trouble with it .. I get the error `Property 'equal' does not exist on type 'typeof Validators'` when compiling on `Validators.dark('#FFF')`. But I even have autocomplete when. typing it on the Editor ... – Miguel Moura Sep 10 '21 at 16:04
  • If you could show me a [mcve], maybe via a web IDE link, I might be able to help; right now though I don't have much insight into what the issue is. – jcalz Sep 10 '21 at 23:21
  • I created an [Example](https://stackblitz.com/edit/angular-ivy-waegqh?file=src/main.ts) that reproduces the problem. It seems even if I am only using `Validators.dark('#FFF')` I still need to import the darkValidator function. And that solves the problem. Any way around this? Shouldn'd `darkValidation` function be available when I add `export` to it? – Miguel Moura Sep 13 '21 at 12:02
  • I just noticed that even with `import` it does compile but when running I get the error: Error: Uncaught (in promise): TypeError: _angular_forms__WEBPACK_IMPORTED_MODULE_6__.Validators.dark is not a function. (In '_angular_forms__WEBPACK_IMPORTED_MODULE_6__.Validators.dark – Miguel Moura Sep 13 '21 at 14:59
  • There are apparently all kinds of drawbacks to trying to augment a module in another module; see [microsoft/TypeScript#12607](https://github.com/microsoft/TypeScript/issues/12607) and issues to which it links. I don't know if there's a canonical way to deal with this. You could try to re-export your modified `Validators` and use that instead, like [this](https://stackblitz.com/edit/angular-ivy-cxjudz?file=src%2Fmain.ts), so you're no longer pretending you're targeting the "base" `Validators` anymore. – jcalz Sep 13 '21 at 15:37