ciekawy
ciekawy

Reputation: 2337

how to create a simple Angular form to get object containing just changed fields

Currently I have an Angular form like

<form>
    Name: <input name="name" [(ngModel)]="data.name"><br>
    Age: <input name="age" [(ngModel)]="data.age"><br>
    City: <input name="city" [(ngModel)]="data.city"><br>
    <button (click)='update()'>
</form>

Upon update I'd like to be able to have an object containing just changed fields. Just for quick and dirty solution I achieved my need by using Proxy which looks more or less like below (with assigning component's data to be changeTracker.proxy):

class ChangeTracker {
    proxy;
    changed;
    constructor(initialTarget = {}) {
        this.changed = {};
        this.proxy = new Proxy(initialTarget, {
            get(target, key) {
                return target[key];
            },
            set(target, key, val) {
                target[key] = val;
                this.changed[key] = val;
                return true;   
            }
        });
    }
}

So eventually I can do i.e. REST PUT

http.put('/some/api/type', { objectId, ...changeTracker.changed });

I was not able to find any SO answer or external tutorial addressing directly the problem of getting set of changes from the form. Wonder what's the Angular way of achieving this - the best would be to avoid unnecessary boilerplate yet looking for any reasonable solution.

Upvotes: 0

Views: 62

Answers (2)

ciekawy
ciekawy

Reputation: 2337

Thanks to Fateh Mohamed answer I got convinced on not so verbose way of using FormBuilder with the proposed template

<form id="personFormId" [formGroup]="personForm">
  Name: <input name="name" formControlName="nameControl"><br>
  Age: <input name="age" formControlName="ageControl"><br>
  City: <input name="city" formControlName="cityControl"><br>
  <button (click)='update()'>
</form>

and achieve the main goal which is to collect the all the changes (and only changes) into one object. Remember the component has to implements OnInit and below is still simplified key part of the solution.

ngOnInit() {
    this.changedData = {};
    this.form = this.formBuilder.group({
        name: [''],
        age: [''],
        city: ['']
    });

    // for ngrx < 6.x mergeMap should be used directly without a pipe
    from(Object.entries<AbstractControl>(this.form.controls))
        .pipe(mergeMap(([key, control]) =>
            control.valueChanges.map(value => ({ [key]: value }))))
        .subscribe(change => {
            this.changedData = {
                ...this.changedData,
                ...change
            }
        });
}

setFormData(data) {
    this.form.patchValue(data);
    this.changedData = {};
}

update() {
    http.put('/some/api/type', { id, ...this.changedData });
    this.changedData = {};
}

Upvotes: 0

Fateh Mohamed
Fateh Mohamed

Reputation: 21377

here is a solution with reactive forms, one of nice things about model driven forms is that it has valueChanges, you can subscribe to forms changes

<form id="personFormId" [formGroup]="personForm">
  Name: <input name="name" formControlName="nameControl"><br>
  Age: <input name="age" formControlName="ageControl"><br>
  City: <input name="city" formControlName="cityControl"><br>
  <button (click)='update()'>
</form>

ts file

public personForm: FormGroup;
constructor(private router: Router, private _fb: FormBuilder) { }
ngOnInit() {
   this.personForm= this._fb.group({
     nameControl: ['', [<any>Validators.required]],
     ageControl: ['', [<any>Validators.required]],
     cityControl: ['', [<any>Validators.required]]
   });

   this.onFormChanges();
  }

form.valueChanges will return an observable

 onFormChanges() {
     this.personForm.valueChanges.distinct().subscribe(val => {
          // do your control here
        });
  }

Upvotes: 1

Related Questions