@ngze/control-value-transformer
TypeScript icon, indicating that this package has built-in type declarations

1.1.0 • Public • Published


codecov MIT commitizen PRs styled with prettier All Contributors ngze spectator

A proper way for transforming ControlValueAccessor values in two-ways

Have you ever needed to transform a value right before passing it to ControlValueAccessor, and transform it back to its original value type after every change? If so, you probably found that it's not straightforward.

Control Value Transformer main purpose is to simplify the way of transforming form/control values by hooking into two lifecycles of ControlValueAccessor, right before it's getting a new value, and after every value change.

Features

  Support two-ways transformation of ControlValueAccessor values
  Super easy to create and use new transformers
  Support both Template Drive and Reactive forms
  Cross-app singleton transformers

Installation

ng add @ngze/control-value-transformer

Add the ControlValueTransformerModule to your AppModule:

import { ControlValueTransformerModule } from '@ngze/control-value-transformer';

@NgModule({
  declarations: [AppComponent],
  imports: [
    FormsModule,
    ReactiveFormsModule, 
    ControlValueTransformerModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Create and Use Control Value Transformers

To create a new control value transformer all you need is to implement the Transformer interface.
Here is an example of a simple transformer that transforms a number into a string via DecimalPipe just before inserting it into an input, and transforms it back to a number on every change:

import { DecimalPipe } from '@angular/common';
import { Transformer } from '@ngze/control-value-transformer';

export class NumberTransformer implements Transformer<number, string> {
  private readonly decimalPipe = new DecimalPipe('en');

  toTarget(number: number): string {
    return this.decimalPipe.transform(number);
  }

  toSource(string: string): number {
    return Number(string.replace(/[^0-9 ]/g, ''));
  }
}

Now you can use it on any component that implements ControlValueAccessor and expects to receive a string as value by using the controlValueTransformer directive:

@Component({
  template: `
    <div>
      <div>You number: {{number}}</h1>
      <input [(ngModel)]="number" [controlValueTransformer]="numberTransformer" />
    <div>
  `
})
class MyComponent {
  number: number;
  numberTransformer = new NumberTransformer();
}

The same NumberTransformer can seamlessly work with FormControl as well:

@Component({
  template: `
    <div>
      <div>You number: {{numberControl.value}}</h1>
      <input [formControl]="numberControl" [controlValueTransformer]="numberTransformer" />
    <div>
  `
})
class MyComponent {
  numberControl = new FormControl();
  numberTransformer = new NumberTransformer();
}

Inputs

@Input Type Description Default
controlValueTransformer Transformer<S, T> | string Control value transformer instance or its name -
rewriteValueOnChange boolean Indicates if writeValue should be called with the transformed value after each onChange call true

Singleton Control Value Transformers

Singleton control value transformers allow you to use a shared transformer instance cross-app.
You can define it simply by decorating your class with ControlValueTransformer:

import { DecimalPipe } from '@angular/common';
import { ControlValueTransformer, Transformer } from '@ngze/control-value-transformer';

@ControlValueTransformer({
  name: 'number'
})
export class NumberTransformer implements Transformer<number, string> {
  private readonly decimalPipe = new DecimalPipe('en');

  toTarget(number: number): string {
    return this.decimalPipe.transform(number);
  }

  toSource(string: string): number {
    return Number(string.replace(/[^0-9 ]/g, ''));
  }
}

Next step is registering the control value transfomer to make it available all over the app:

import { ControlValueTransformerModule } from '@ngze/control-value-transformer';

import { NumberTransformer } from './number.transformer';

@NgModule({
  declarations: [AppComponent],
  imports: [
    FormsModule,
    ReactiveFormsModule, 
    ControlValueTransformerModule.register([NumberTransformer])
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Now you can use the unique name (number) instead of passing transformer instance into controlValueTransformer directive:

@Component({
  template: `
    <div>
      <div>You number: {{number}}</h1>
      <input [(ngModel)]="number" [controlValueTransformer]="'number'" />
    <div>
  `
})
class MyComponent {
  number: number;
}

Using Dependencies Injection

By default, registered control value transformers can be injected as traditional providers:

@Component(...)
class MyComponent {
  constructor(private readonly numberTransformer: NumberTransformer) {}
  ...
}

Adding Injectable on the transformer class will allow you to inject any available provider:

import { Injectable, Inject, LOCALE_ID } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { ControlValueTransformer } from '@ngze/control-value-transformer';

@Injectable()
@ControlValueTransformer({
  name: 'number'
})
export class NumberTransformer implements Transformer<number, string> {
  private readonly decimalPipe = new DecimalPipe(this.localId);

  constructor(@Inject(LOCALE_ID) private readonly localId: string) {}

  toTarget(number: number): string {
    return this.decimalPipe.transform(number);
  }

  toSource(string: string): number {
    return Number(string.replace(/[^0-9 ]/g, ''));
  }
}

Contributors

Thanks goes to these wonderful people (emoji key):


Zeev Katz

💻 📖 🤔 🚧

This project follows the all-contributors specification. Contributions of any kind welcome!

Package Sidebar

Install

npm i @ngze/control-value-transformer

Weekly Downloads

0

Version

1.1.0

License

MIT

Unpacked Size

195 kB

Total Files

38

Last publish

Collaborators

  • zeevkatznpm