Not able to trim input text field using Angular Forms 7

Viewed 44

I am using Angular forms(7.2.10) with Parent / child components in my Angular 7 application. I am trying to trim the text field input(String) from leading and trailing whitespaces but can't get it working.

Here is my code:

Parent component:

@Component({
    selector: 'basicorder',
    template: `
        <content-header [title]="title">
            <spinner-button id="submitOrderButton" (onClick)="onSubmit.emit()">
                Submit
            </spinner-button>
        </content-header>
        <form [formGroup]="form" novalidate id="orderForm" title="Details" class="form form-horizontal">
            <form-field label="Order ID">
                <input type="text" id="orderIdInput" formControlName="orderId" class="form-control" Placeholder="Order ID">
            </form-field>

            <form-field label="Order Reference">
                <input id="yourRefInput" formControlName="yourRef" class="form-control" Placeholder="Order Reference">
            </form-field>
        </form>
    `
})

export class BasicOrderComponent {

    @Input()
    title: string;

    @Input()
    form: FormGroup;

    @Output()
    onSubmit: EventEmitter<any> = new EventEmitter();

}

@Component({
    selector: 'form-field',
    template: `
        <div class="form-group row" [ngClass]="{'align-items-center': static}">
            <label *ngIf="label" class="col-4 col-form-label text-right text-muted">{{label}}</label>
            <div class="col-8" [ngClass]="{'offset-4': !label, 'form-control-static': static}">
                <ng-content></ng-content>
            </div>
        </div>
    `
})
export class FormFieldComponent {
    @Input()
    label: string;
    @Input()
    static: boolean;
}

Child Component:

@Component({
    template: `
        <basicorder title="Form title"
                    [form]="orderForm"
                    [processing]="processing"
                    (onSubmit)="submit()">
        </basicorder>
        <form [formGroup]="orderForm" novalidate id="modify" title="Details" class="form form-horizontal">
            <form-field label="Service ID">
                <input id="serviceIdInput" formControlName="serviceId" class="form-control">
            </form-field>
        </form>
    `
})
export class ChildComponent implements OnInit {
    public orderForm: FormGroup;
    public options: any;


    constructor(public formBuilder: FormBuilder, ...other fields) {
        ... initialize fields
    }

    ngOnInit() {

        this.orderForm = this.formBuilder.group({
            orderId: [null, Validators.required],
            yourRef: null
        });
    }


    submit(): void {
        let orderId =  this.orderForm.controls['orderId'].value;
        // I expect orderId to be trimmed from spaces but it still contains spaces.
        
        ... other actions
    }


}

I want to trim whitespaces on orderIdInput field from parent component.

 <form-field label="Order ID">
     <input type="text" id="orderIdInput" formControlName="orderId" class="form-control" Placeholder="Order ID">
 </form-field>

Tried these so far but didn't work:

1> <input type="text" ng-trim="true" ...>
2> <input name="orderId" ngModel="orderId1" (change)="orderId1=orderId1.trim()"/>
1 Answers

Hello great question and it's nice to see when someone actually tried something before posting a question.

About the first thing that you tried, as far as I know, ng-trim does not exist in angular 2+.

About the second thing that you tried, in order for the ngModel to work, you have to bind a variable to the model, e.g. use the following syntaxis

<input [(ngModel)]="variableInsideTheComponent" />

The second thing is that ngModel on change emits trough the following event : ngModelChange so instead for change you have to listen for ngModelChange.

So lets go with some solutions to your issue, first lets solve the reactive form case. My suggestion for you is to create a directive, that will trim the input values. Such directive can look like so :

@Directive({
  selector: '[appTrim]',
})
export class TrimDirective implements OnInit, OnDestroy {
  destroy$ = new Subject();
  constructor(private control: FormControlName) {}

  ngOnInit() {
    this.control.valueChanges
      .pipe(takeUntil(this.destroy$), distinctUntilChanged())
      .subscribe((x) => {
        this.control.control.setValue(x.trim());
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
  }
}

So let's see what this thing do. First it gets reference to the formControl attached to the input that we will be trimming, then it subscribes for any changes happening to the value of the input. After that we add the distinctUntilChanged operator so that whenever we trim and update the control value we don't go trough the whole triming process once again (if distinctUntilChanged is not used we will go to an infinite loop). At the end inside of the subscription, we are trimming the value that comes from the input and update the input itself with the trimmed value.

Once you have this directive you can use it as so

<form [formGroup]="form">
  <input appTrim formControlName="field" />
</form>

Now about the template approach.

<input [(ngModel)]="field2" (ngModelChange)="handleChange($event)" />

@Component({...})
export class AppComponent {
  field2 = 'Angular';

  handleChange(e: string) {
    setTimeout(() => {
      this.field2 = e.trim();
    }, 0);
  }
}

Here if you use the correct event and the two-way binding you can achieve the thing that you described in the second case (from the things you tried). The only tricky part is the setTimeout part, as there are some race conditions happening about what is updated when this artificial delay is needed for the trimming to work.

Woking Stackblitz

Disclaimer: About the two way binding solution, probably it can be improved but it is beyond me :D

Related