22

What is the way of doing parsers and formatter in Angular2?

in Angular1 it was possible to do such manipulations with ngModelController:

//model -> view
ngModelController.$formatters.push(function(modelValue) {
  return modelValue.toUpperCase();
});

//view -> model
ngModelController.$parsers.push(function(viewValue) {
  return viewValue.toLowerCase();
});

could you provide me an example how to do it with Angular2?


UPD: Pipes are similar to Filters in Angular1, but I'm looking not for Filters, but for Parsers and Formatters for ngModel. So "Pipes" is not correct answer.

Stepan Suvorov
  • 25,118
  • 26
  • 108
  • 176
  • @egaga what do you want to know what is not yet in the answer? – Günter Zöchbauer Dec 01 '16 at 09:53
  • According to angular 2 official documentation, pipes are renamed angular 1 filters. You don't use filters in angular 1 to convert viewModel to model, and vice versa. Mostly you use filters to filter or format data for templates, not for two-way data passing. And how does validation relate to pipes? If user change is invalid, will it be piped anyway? That is hardly what would be wanted. I want to see a custom component with validation. The component should have different representation for user editable value, and different representation for model value that is passed outside the component. – egaga Dec 01 '16 at 10:33
  • I added an answer what I think is what you are looking for. If you leave comments about what is unclear I try to explain more. – Günter Zöchbauer Dec 01 '16 at 13:24
  • 1
    I really miss the formatter and parser pipelines. There should be an outcry now that these are missing. – John Strickler Apr 24 '17 at 19:14

5 Answers5

14

could you provide me an example how to do it with Angular2?

                                            a.) model -> view


     1> Using Pipes

TS:

myString: string = "ABCDEFGH";

Template:

{{myString | lowercase}}

Output:

abcdefgh

     2> Using transformation directly

Template:

Below Input field will have lowercase string as value

<input type="text" [value]="myString.toLowerCase()">

I'm also lowercase:  {{myString.toLowerCase()}}

Output:

Input field with value "abcdefgh"

I'm also lowercase:  abcdefgh

     3> Using Getter/Setter

TS:

myString: string = "ABCDEFGH";

get stillMyString() {
  return this.myString.toLowerCase();
}
set stillMyString(v) {
  this.myString = v;
}

Template:

{{stillMyString}}

Output:

abcdefgh

     4> Using Directives


     5> Using ControlValueAccessor


     OR using a combination of any of the above


                                            b.) view -> model


     1> Using Output/Events

Template:

Below Input field will get lowercase string as value but will store uppercase string

<input type="text" [value]="myString.toLowerCase()" (change)="myString = $event.toUpperCase()">

I'm give uppercase values automatically:  {{myString}}

Output:

Input field with initial value "abcdefgh"

I'm given uppercase values automatically:  ABCDEFGH

     2> Using Setter/Getter

TS:

myString: string = "ABCDEFGH";

get stillMyString() {
  return this.myString;
}
set stillMyString(s) {
  this.myString = s.toUpperCase();
}

Template:

Below Input field will get lowercase string as value but will store uppercase string

<input type="text" [value]="stillMyString.toLowerCase()" (change)="myString = $event">

Now I'm Uppercase:  {{stillMyString}}

Output:

Input field with initial value "abcdefgh"

I'm given uppercase values automatically:  ABCDEFGH

AND/OR a combination of above methods and any other method that I can't think of right now.


                                            Wrapping UP

  • As you can see there are multiple ways to do the same thing, it just depends upon your need and choice to use any of it.

  • Validation is not the concern of transformation, but you can do it by improving upon the getter/setters and using FormControl on your input fields

  • I could just show you the tip of the iceberg here, there is so much to model <> view transformations, becasuse that's what Angular does, Data Binding, one way or two way.

Hope It Helps :)

Community
  • 1
  • 1
Ankit Singh
  • 24,525
  • 11
  • 66
  • 89
  • And if the value is not possible to be transformed, e.g. string to number, how is that invalidity propagated to angular form mechanism? – egaga Dec 01 '16 at 15:10
  • angular forms can do with `Input/Output` bindings, and they can also have validations, there are also multiple ways to do that, one of which is `FormControl`. other then that I don't think that forms care about the value they are inserted with. I'm not sure if I got your point ?! – Ankit Singh Dec 02 '16 at 04:36
  • I think this is a bad answer as it present several choices but doesn't highlight what are the benefits of each one or if there is a preferred method to do it in the current versions. – Agustin Cautin Oct 05 '18 at 07:43
  • An example for FormControl is missing to be the perfect answer – Serginho Jun 04 '20 at 14:17
5

There is no such concept of formatters or parsers in Angular 2 according to me but you can implement it using following code, its very simple

In HTML

 <input type="text"  [ngModel] = "formatter(ex)"  (ngModelChange)="parser($event)">

in code

export class Component{
     data:string = 'data';
     constructor(){}

     formatter(value){
        value = value.toUpperCase();   //manipulate the data according to your need
        return value;
     }

     parser(value){
         this.data = value.toLowerCase();  //manipulate the data according to your need
     }



}

you can also implement array of functions according to your need to fully implement $formatters and $parsers.

Vikash Dahiya
  • 5,741
  • 3
  • 17
  • 24
  • @egaga hope this will solve your problem. Tell me your scenario if you are not satisfied by this solution. – Vikash Dahiya Dec 06 '16 at 22:44
  • Exactly what I needed! Thx – M'sieur Toph' May 30 '17 at 08:48
  • @VikashDahiya can you confirm where the ex parameter comes from in the element above? How will it know to use the value from the input's view? – jessewolfe Aug 23 '22 at 20:04
  • @VikashDahiya I found the answer to above: Your call to formatter in the input tag should be ````formatter(data)```. Note also that (at least in Angular 14) I had to have ````import { FormsModule } from '@angular/forms'; ```` in my Angular module and have it added to the import: attribute of @NgModule of same .module.ts for this to work, as it's required to get ngForms directive. – jessewolfe Aug 23 '22 at 20:15
2

According to angular 2 official documentation, pipes are renamed angular 1 filters. You don't use filters in angular 1 to convert viewModel to model, and vice versa. Mostly you use filters to filter or format data for templates, not for two-way data passing.

I have never worked with Angular1 and don't know how stuff works there.

I think what you are looking for is ControlValueAccessor that makes custom components work with ngModel and Angular2 forms and allows to map between display and model format of the value.

Plunker example from https://github.com/angular/angular/issues/10251#issuecomment-238737954

This is a bit more complex example than your question might be about, where a component contains a sub-form. A component can just be a form control as well.

The Plunker also doesn't yet use the now required NgModule but should be easy enough to migrate (I'll have another look myself later)

And how does validation relate to pipes? If user change is invalid, will it be piped anyway? That is hardly what would be wanted.

Validation doesn't relate to pipes at all.

I want to see a custom component with validation. The component should have different representation for user editable value, and different representation for model value that is passed outside the component.

The Address component above also implements custom validation

From the comment in https://github.com/angular/angular/issues/10251#issuecomment-238737954

Adding validation has a similar process to value accessors. You implement the validator interface (just a validate() function) and provide it as an NG_VALIDATOR on your custom form control. Here's an example:

See also

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • It seems that invalid values are propagated to the template output (form | json). Let's say that I would need to preview the address details using another component (before submitting Address). The preview component would need the address object in different object representation. e.g. APreview { street: string, zip: number }, and the fields of APreview must exist! How would you transform the Address-object into APreview and give it to AddressPreviewComponent { @Input preview: APreview }. The point being that only valid and transformable values should be propagated to the AddressPreview. – egaga Dec 01 '16 at 15:15
  • What do you mean by "It seems that invalid values are propagated to the template output (form | json)". Can you demonstrate in the Plunker? What do you mean by "and the fields of APreview must exist". You mean they are mandatory? I guess `Object.assign()` should do to create APreview. Besides that I don't think this is where Angular provides anything. You can use was TypeScript provides to transform objects. "only valid and transformable values should be propagated". If the fields are mandatory and the transformation is done on submit all required fields need to be there. – Günter Zöchbauer Dec 01 '16 at 15:33
  • Something like this: https://plnkr.co/edit/TfCxm0F5XnsWelLSL0LV?p=preview The outside of component values should only ever be valid values. – egaga Dec 01 '16 at 17:36
  • I still have troubles what you try to accomplish. At what point should NaN be converted to `10`? If you have two-way binding and pass `NaN` in but get `10` out, then this will propagate back in. – Günter Zöchbauer Dec 03 '16 at 15:24
  • An approach https://plnkr.co/edit/GZmcUYQiPb0OyXpTYafE?p=preview. Some feedback about why this is or is not what you want might help to get to a solution. – Günter Zöchbauer Dec 03 '16 at 15:35
  • The problem I have with this is that the pipe is not encapsulated into the custom-input-component. Using this approach one is forced to make two custom-inputs: the inner one and outer one that has the additional pipe "validation". But I think functionally you got it close, though not really happy about it still. Thank you for the effort, so far. – egaga Dec 05 '16 at 08:47
  • An alternative approach https://plnkr.co/edit/gjZ0zZAtIqwcBCBdQpx5?p=preview. `asNumber` and `valueChangeAsNumber` are two different ways of getting the sanitized value from the input. – Günter Zöchbauer Dec 05 '16 at 09:07
0

Methods writeValue(val: T) and updateChanges are your parsers and formatters

Much more heavy in ng2+ (honestly taken from ng9 project) look ngModel extension, here is short monster sample (not runnable - Stack Overflow not supporting ng2+):

import { Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'multi-text-input',
  styles: [``],
  template: `
    <div *ngFor="let val of inputs; let idx = index" style="display: flex">
      <input type="text" pInputText [(ngModel)]="val.val" (ngModelChange)="updateChanges()" (mousedown)="onTouched()" />
    </div>
  `,
  // thx to google for this providers section below
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MultiTextInput),
      multi: true,
    },
  ],
})
export class MultiTextInput<T = string | string[]> {
  inputs: { val: string }[] = [];
  @Input() separator = ',';

  writeValue(value): void {
    value = value || '';
    const val: string[] = Array.isArray(value) ? value : value.split(this.separator);
    this.inputs = val.filter((val) => val.trim()).map((val) => ({ val }));

    this.value = value;
  }

  updateChanges() {
    const arr = this.inputs.map(({ val }) => val.trim()).filter(Boolean);
    // ts-ignore to match T, but u don't even need it
    // @ts-ignore
    this.onChange(Array.isArray(this.value) ? arr : arr.join(this.separator));
  }

  // thx to google for all generic lines below
  value: T;

  registerOnChange(fn): void {
    this.onChange = fn;
  }

  registerOnTouched(fn): void {
    this.onTouched = fn;
  }

  onChange: (_: T) => void = (_: T) => {};
  onTouched: () => void = () => {};
}

Import, then <multi-text-input [(ngModel)]="variable"/> instead of ngModel can be formControl attribute also. If this is not working for you, please comment, and I will give a better sample.

halfer
  • 19,824
  • 17
  • 99
  • 186
sonnenhaft
  • 1,638
  • 1
  • 13
  • 15
-3

In Angular2 you use pipes. See documentation: https://angular.io/docs/ts/latest/guide/pipes.html

rook
  • 2,819
  • 4
  • 25
  • 41