Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular form change event with material components

I have a form that contains inputs and material components (like mat-select or mat-checkbox).

Each time a modification is made by the user, I want to persist them to the DB. So I did something like <form (change)="save()">.

This is working flawlessly for the native inputs, but it doesn't fire when the user changes a material component value.

I'd rather avoid solutions like using <mat-select (selectionChange)="save()"> on every component, as I could easily forget to add it when I'll have to update my form.

Edit

This is a template driven form. My template is as follow:

<form (change)="save()">
    <!-- Will trigger save -->
    <mat-form-field class="col">
        <input matInput placeholder="Name" name="name" [(ngModel)]="item.name">
    </mat-form-field>

    <!-- Will NOT trigger save -->
    <mat-form-field class="col">
        <mat-select placeholder="Category" name="category [(ngModel)]="item.category.id">
            <mat-option *ngFor="let category of categories" [value]="category.id">{{category.name}}</mat-option>
        </mat-select>
    </mat-form-field>

    <!-- ... -->
</form>

The component code has nothing particular, only the model variable (item: Item;).

like image 463
Sébastien Avatar asked Nov 20 '18 15:11

Sébastien


People also ask

What is Matinput?

Matinput is an Angular directive that primarily allows input and text area elements to work with a form field. With this, you can display placeholders perfectly, add custom error messages, a clear button, specify the maximum length of the text or add prefixes and suffixes for a seamless user experience.

What is MatFormFieldControl?

MatFormFieldControl. An interface which allows a control to work inside of a MatFormField .

What is matSuffix?

html - matSuffix makes mat-icon not horizontally centered in mat-icon-button - Stack Overflow.

How detect changes in an input field as we type in Angular?

Whenever your input is changed, angular accesses the variable the string is set in. One way to do something when the variable changes would be to use a get ter and set ter. Everything else remains the same. And you just have to use ngModel on text.


3 Answers

this solution works for Angular 11.2.1 and Material 11.2.1

You need to capture the changes with valueChanges observable

this.editForm.valueChanges
 .subscribe(value=> {
       if (this.editForm.dirty) {
              //do something
              }
           });

Notes to observe:

  1. typically you don't need to put the filter if(this.editForm.dirty) {...} , because the normal behavior is this observer should kick in only if it is changed (dirty), but due to the material components mat-xxx, this observer is kicked in few times after the value changed of the field , so to avoid multiple unnecessary execution to the event you would have to filter the observable by this condition if (this.editForm.dirty) {...} it will execute only once it is changed.

again this works with reactive forms, I just noticed your question uses Template-driven but worth noting just in case someone is looking at solution for reactive forms.

like image 59
Timy Shark Avatar answered Oct 20 '22 11:10

Timy Shark


Using a change tracker you can solve this problem.

Check the following example:

import { Component , KeyValueChanges, KeyValueDiffer, KeyValueDiffers, DoCheck, OnInit } from '@angular/core';

@Component({
  selector: 'input-overview-example',
  styleUrls: ['input-overview-example.css'],
  templateUrl: 'input-overview-example.html',
})
export class InputOverviewExample implements OnInit, DoCheck {
  categories = [
    { id: 'id-1', name: 'Category 1' },
    { id: 'id-2', name: 'Category 2' },
    { id: 'id-3', name: 'Category 3' },
  ] as ExampleCategory[];
  item = new ExampleItem('Item One', this.categories[0].id);
  itemDiffer: KeyValueDiffer<string, any>;

  constructor(private readonly differs: KeyValueDiffers) { }

  ngOnInit() {
    this.itemDiffer = this.differs.find(this.item).create();
  }

  ngDoCheck(): void {
    const changes = this.itemDiffer.diff(this.item);
    if (changes) {
      //TODO: Save data here
      console.log("changed to: " + JSON.stringify(this.item));
    }
  }
}

export class ExampleItem {
  constructor(
    public name: string,
    public categoryId: string) {
  }
}

export class ExampleCategory {
  constructor(
    public id: string,
    public name: string) {
  }
}

And the component HTML:

<form>
    <mat-form-field class="col">
        <input matInput placeholder="Name" name="name" [(ngModel)]="item.name">
    </mat-form-field>

    <mat-form-field class="col">
        <mat-select placeholder="Category" name="category" [(ngModel)]="item.categoryId" required>
            <mat-option *ngFor="let category of categories" [value]="category.id">{{category.name}}</mat-option>
        </mat-select>
    </mat-form-field>
</form>

I hope this helps!

like image 41
dbvega Avatar answered Oct 20 '22 11:10

dbvega


you can still wrap a <form (change)="save()" [formGroup]="form"> around it

then use a <mat-form-field> around your other mat components. it should listen on the formgroup. The components can get an identifyer with formControlName=""

like image 36
Nikolai Kiefer Avatar answered Oct 20 '22 11:10

Nikolai Kiefer