Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 keep input form value in bind with the model using getter and setter

I would like to bind an input value to the model using getter and setters. In this way i can prevent and/or manipulate the input's value while writing inside it.

For example i want the prevent numbers inside an input box. So, if write 'abc' all is ok, then if I start writing a number nothing should happen (to the model and to the input's value). The issue is that with the following code i'm able to write anything inside the input box (but the model it's correct). This means that the input box value is not really representing my model.

NOTE: The reason beyond this questions is that I want to use my models to validate forms, preventing for example specific characters. I would like to not use reactive forms as i want to keep my validations inside my models not components. Also note that in a real scenario i would have a UserModel class with inside name and other fields with their validations.

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2><input type="text" [(ngModel)]="name"> {{name}}</h2>
    </div>
  `,
})
export class App {
  _name:string = 'ss';
  constructor() {
  }

  // In real scenario those 2 methods are in a separate class UserModel
  get name() {
    return this._name;
  }

  set name() {
    if ((new RegExp(/^[a-zA-Z]*$/).test(val))) {
        this._name = val;
    }
  }
}
like image 474
Marco Turi Avatar asked Mar 17 '17 10:03

Marco Turi


2 Answers

If you manipulate the value in the setter, this can cause issues with change detection, so that ngModel doesn't pick up the changes and doesn't update the <input>

To work around you can use

export class App {

  _name:string = 'ss';

  constructor(private cdRef:ChangeDetectorRef) {}

  get name() {
    return this._name;
  }

  set name(value:String) {
    this._name = value + 'x';
    this.cdRef.detectChanges()
  }
}

if you reset the value to the previous value, you might need to pass an artificial different value first, otherwise change detection won't detect a change and even detectChanges() won't update the input.

  set name(value:String) {
    var oldVal = this._name;
    this._name = null;
    this.cdRef.detectChanges()
    this._name = oldVal;
    this.cdRef.detectChanges()
  }
like image 103
Günter Zöchbauer Avatar answered Sep 27 '22 19:09

Günter Zöchbauer


Based on @Günter Zöchbauer answer i made a workaround. It's not definitive and could be more abstract, but for now it's ok.

export class App implements OnInit {
    @Input() userModel: UserModel = null;
    public _vm;

    constructor(private _changeDetectionRef: ChangeDetectorRef) {
    }

    /**
     * Initalize view model, it's important to keep names specular
     */
    ngOnInit() {
        this._vm = {
            name: this.userModel.name,
            surname: this.userModel.surname,
        };
    }

    /**
     * Helper for avoid detectchanges inside the modal, and reduce boilerplate. We could also ad an interface/type of the possibile field value, ie type fieldT= 'name' | 'surname';
     * @param field
     * @param val
     */
    protected updateModel(field, val: string): void {
        this._vm[field] = null;
        this._changeDetectionRef.detectChanges();
        this.userModel[field] = val;
        this._vm[field] = this.userModel[field];
        this._changeDetectionRef.detectChanges();
    }
}

In userModel:

....

    public get name(): string {
        return this.name';
    }

    public set name(val: string) {
        if ((new RegExp(/^[a-zA-Z]*$/).test(val))) {
            this.name = val;
        }
    }

In template:

<input type="text"  name="userName"  [ngModel]="_vm.name" (ngModelChange)="updateModel('name', $event)">
like image 34
Marco Turi Avatar answered Sep 27 '22 20:09

Marco Turi