diff --git a/README.md b/README.md index bc63a69..ec30119 100644 --- a/README.md +++ b/README.md @@ -12,25 +12,208 @@ [![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)]() [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-) +[![ngze](https://img.shields.io/badge/@-ngze-383636?style=flat-square&labelColor=474768)](https://github.com/ngze/) +[![spectator](https://img.shields.io/badge/tested%20with-spectator-2196F3.svg?style=flat-square)]() -> A seamless way to transform ControlValueAccessor values in two-ways. +> 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 -## Installation +✅  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 ``` -# Using ng ng add @ngze/control-value-transformer +``` + +Add the `ControlValueTransformerModule` to your `AppModule`: + +```ts +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: + +```ts +import { DecimalPipe } from '@angular/common'; +import { Transformer } from '@ngze/control-value-transformer'; + +export class NumberTransformer implements Transformer { + 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: + +```ts +@Component({ + template: ` +
+
You number: {{number}} + +
+ ` +}) +class MyComponent { + number: number; + numberTransformer = new NumberTransformer(); +} +``` + +The same `NumberTransformer` can seamlessly work with `FormControl` as well: +```ts +@Component({ + template: ` +
+
You number: {{numberControl.value}} + +
+ ` +}) +class MyComponent { + numberControl = new FormControl(); + numberTransformer = new NumberTransformer(); +} +``` + +### Inputs + +| @Input | Type | Description | Default | +|-------------------------|--------------------------------|--------------------------------------------------------------------------------------------------|---------| +| controlValueTransformer | `Transformer \| 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`: + +```ts +import { DecimalPipe } from '@angular/common'; +import { ControlValueTransformer, Transformer } from '@ngze/control-value-transformer'; + +@ControlValueTransformer({ + name: 'number' +}) +export class NumberTransformer implements Transformer { + 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: + +```ts +import { ControlValueTransformerModule } from '@ngze/control-value-transformer'; -# Using yarn -yarn add @ngze/control-value-transformer +import { NumberTransformer } from './number.transformer'; -# Using npm -npm i @ngze/control-value-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: + +```ts +@Component({ + template: ` +
+
You number: {{number}} + +
+ ` +}) +class MyComponent { + number: number; +} +``` + +### Using Dependencies Injection + +By default, registered control value transformers can be injected as traditional providers: +```ts +@Component(...) +class MyComponent { + constructor(private readonly numberTransformer: NumberTransformer) {} + ... +} ``` -### Usage +Adding `Injectable` on the transformer class will allow you to inject any available provider: + +```ts +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 { + 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 ✨