1.1.0 • Published 3 years ago

@ngze/control-value-transformer v1.1.0

Weekly downloads
1
License
MIT
Repository
github
Last release
3 years ago

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

@InputTypeDescriptionDefault
controlValueTransformerTransformer<S, T> \| stringControl value transformer instance or its name-
rewriteValueOnChangebooleanIndicates if writeValue should be called with the transformed value after each onChange calltrue

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):

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