Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Angular Form very slow within MatDialog component?

I have an Angular form in a Material Dialog component. There is data being two-way binded and when tabbing through inputs or typing in inputs results in the screen locking up for a few seconds between keydown's . All data is passing properly but is painfully slow while trying to use the form.

I've tried refactoring the form to use for the inputs to use a "Material form" but still has the same slowdown performance.

Here's a screenshot of performance tracker in chrome: Slow down performance over 10 seconds.

Is there something wrong with my configuration? Or is this a possible regression in latest Angular 8 animation / CDK packages? Here are my Angular package dependencies:

dependencies": {
    "@angular/animations": "^8.2.13",
    "@angular/cdk": "^8.2.3",
    "@angular/common": "~8.2.13",
    "@angular/compiler": "~8.2.13",
    "@angular/core": "~8.2.13",
    "@angular/forms": "~8.2.13",
    "@angular/material": "^8.2.3",
    "@angular/platform-browser": "~8.2.13",
    "@angular/platform-browser-dynamic": "~8.2.13",
    "@angular/router": "~8.2.13",
}

Here is the component method that calls the dialog:

public editRow(tablerow: IRule): void {
const dialogRef = this.dialog.open(EditDialogComponent, {
  width: '100%',
  height: '85%',
  data: tablerow
});

this.subscriptions.push(
  dialogRef.afterClosed().subscribe(updatedRule => {
    if (updatedRule !== undefined) {

      this.rules = this.rules.map(rule => rule.Id === updatedRule.Id ? updatedRule : rule);

      this.subscriptions.push(this.dataService.updateRule(updatedRule).subscribe(
        response => {
          this.snackBar.openFromComponent(SuccessComponent, {
            duration: 3000,
            data: `Rule added`
          });
        }, error => {
          this.snackBar.openFromComponent(ErrorComponent, {
            duration: 10000,
            data: 'Internal Server Error'
          });
          }
        ));
      }
    })
  );
}

The mat dialog template containing the form:

<mat-dialog-content>
<i id="close-icon" class="material-icons md-24" aria-label="close"
[mat-dialog-close]>close</i>
<div class="brand-panel-container">
<div class="brand-panel">
  <div class="brand-panel-header">
    <div class="brand-title">
      <h4 mat-dialog-title>Rule: {{ data.Id }}</h4>
    </div>
  </div>
  <form #ruleForm="ngForm">
    <div class="row">
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Shop Type:<span class="asterisk">*</span></label>
          <select
            [(ngModel)]="data.Type.Text"
            value="{{ data.Type.Text }}"
            name="type"
            type="text"
            class="form-control"
            id="type"
            required>
            <option *ngFor="let opt of shopTypeOpts; trackBy: indentify" value="{{opt.Text}}">{{opt.Text}}</option>
          </select>
        </div>
      </div>
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Origin:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.Origin"
            value="{{ data.Origin }}"
            name="origin"
            type="text"
            class="form-control"
            id="origin"
            required>
        </div>
      </div>
    </div>

    <div class="row">
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Destination:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.Destination"
            value="{{ data.Destination }}"
            name="destination"
            type="text"
            class="form-control"
            id="destination"
            required>
        </div>
      </div>
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Fare:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.Fare.Text"
            value="{{ data.Fare.Text }}"
            name="fare"
            type="text"
            class="form-control"
            id="fare"
            required>
        </div>
      </div>
    </div>

    <div class="row">
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Government:<span class="asterisk">*</span></label>
          <select
            [(ngModel)]="data.Government.Text"
            value="{{ data.Government.Text }}"
            name="government"
            type="text"
            class="form-control"
            id="government"
            required>
            <option *ngFor="let opt of governmentTypeOpts; trackBy: indentify"
              value="{{opt.Text}}">{{opt.Text}}</option>
          </select>
        </div>
      </div>
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Special Pricing:<span class="asterisk">*</span></label>
          <select
            [(ngModel)]="data.SpecialPricing.Text"
            value="{{ data.SpecialPricing.Text }}"
            name="specialPricing"
            type="text"
            class="form-control"
            id="specialPricing"
            required>
            <option *ngFor="let opt of specialPricingTypeOpts; trackBy: indentify"
              value="{{opt.Text}}">{{opt.Text}}</option>
          </select>
        </div>
      </div>
    </div>

    <div class="row">
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Upgrade:<span class="asterisk">*</span></label>
          <select
            [(ngModel)]="data.Upgrade.Text"
            value="{{ data.Upgrade.Text }}"
            name="upgrade"
            type="text"
            class="form-control"
            id="upgrade"
            required>
            <option *ngFor="let opt of upgradeTypeOpts; trackBy: indentify"
              value="{{opt.Text}}">{{opt.Text}}</option>
          </select>
        </div>
      </div>
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Cabin Count:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.CabinCount"
            value="{{ data.CabinCount }}"
            name="cabinCount"
            type="text"
            class="form-control"
            id="cabinCount"
            required>
        </div>
      </div>
    </div>

    <div class="row">
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Columns Count:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.ColumnsCount"
            value="{{ data.ColumnsCount }}"
            name="columnsCount"
            type="text"
            class="form-control"
            id="columnsCount"
            required>
        </div>
      </div>
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Lang Code:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.LangCode"
            value="{{ data.LangCode }}"
            name="langCode"
            type="text"
            class="form-control"
            id="langCode"
            required>
        </div>
      </div>
    </div>

    <div class="row">
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Fare Wheel Search?:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.IsFareWheelSearch"
            value="{{ data.IsFareWheelSearch }}"
            name="isFareWheelSearch"
            type="text"
            class="form-control"
            id="isFareWheelSearch"
            required>
        </div>
      </div>
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Markets:<span class="asterisk">*</span></label>
          <select
            [(ngModel)]="data.Markets.Text"
            value="{{ data.Markets.Text }}"
            name="markets"
            type="text"
            class="form-control"
            id="markets"
            required>
            <option *ngFor="let opt of marketTypeOpts; trackBy: indentify" value="{{opt.Text}}">{{opt.Text}}</option>
          </select>
        </div>
      </div>
    </div>

    <div class="row">
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">POS:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.POS"
            value="{{ data.POS }}"
            name="pos"
            type="text"
            class="form-control"
            id="pos"
            required>
        </div>
      </div>
      <div class="col-md-6">
        <div class="form-group">
          <label for="name">Columns:<span class="asterisk">*</span></label>
          <input
            [(ngModel)]="data.Columns"
            value="{{ data.Columns }}"
            name="columns"
            type="text"
            class="form-control"
            id="columns"
            required>
        </div>
      </div>
    </div>

    <div mat-dialog-actions>
      <span *ngIf="!ruleForm.valid" class="invalid-msg"><span
          class="asterisk">*</span>All fields must be filled in to save
        changes.</span>
      <button mat-button class="brand-default-button"
        [mat-dialog-close]>Cancel</button>
      <button mat-button class="brand-confirm-button" type="submit"
        [disabled]="!ruleForm.valid" [mat-dialog-close]="data.Id"
        (click)="onSaveData(ruleForm.value)">Save Changes</button>
    </div>
  </form>
</div>

The dialog component file:

import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { IRule } from '../../../models/rule.interface';
import { OptionsService } from 'src/app/shared/services/options.service';
import { IDropdownOption } from 'src/models/dropdown-option.interface';

@Component({
  selector: 'app-edit-dialog',
  templateUrl: './edit-dialog.component.html',
  styleUrls: ['./edit-dialog.component.scss']
})
export class EditDialogComponent {

  public shopTypeOpts: IDropdownOption[] = [];
  public governmentTypeOpts: IDropdownOption[] = [];
  public specialPricingTypeOpts: IDropdownOption[] = [];
  public fareTypeOpts: IDropdownOption[] = [];
  public upgradeTypeOpts: IDropdownOption[] = [];
  public marketTypeOpts: IDropdownOption[] = [];

  constructor(
    public dialogRef: MatDialogRef<EditDialogComponent>,
    public optionsService: OptionsService,
    @Inject(MAT_DIALOG_DATA) public data: IRule) {
    this.shopTypeOpts = this.optionsService.shopTypeOptions;
    this.governmentTypeOpts = this.optionsService.governmentTypeOptions;
    this.specialPricingTypeOpts = this.optionsService.specialPricingTypeOptions;
    this.fareTypeOpts = this.optionsService.fareTypeOptions;
    this.upgradeTypeOpts = this.optionsService.upgradeTypeOptions;
    this.marketTypeOpts = this.optionsService.marketTypeOptions;
  }

  public onSaveData(updatedRule: IRule): void {
    this.dialogRef.close(updatedRule);
  }

  public indentify(index, item) {
    return item.Text;
  }

}

IDropdownOption interface:

export interface IDropdownOption {
  Text: string;
  Value: number;
}

*EDITED to include trackBy function & IDropdownOption interface to see unique identifier. *

The slow down seems to be because of the dropdown options being looped over repeatedly... Maybe changeDetection strategy needs to be changed?

like image 480
Ares Avatar asked Nov 05 '25 05:11

Ares


1 Answers

Thanks so much for everyone's help. I solved my issue by using ChangeDetectorRef in the parent component to detach once the dialog is opened, and reattached once the dialog is closed. This prevents any re-render / re-drawing of the EditDialogComponent and fixes the performance issue.

public editRow(tablerow: IRule): void {

this.changeDetectorRef.detach(); // Detach change detection before the dialog opens. 

const dialogRef = this.dialog.open(EditDialogComponent, {
  width: '100%',
  height: '85%',
  data: tablerow
});

this.subscriptions.push(
  dialogRef.afterClosed().subscribe(updatedRule => {

    this.changeDetectorRef.reattach(); // Reattach change detection after the dialog closes.

    if (updatedRule !== undefined) {

      this.rules = this.rules.map(rule => rule.Id === updatedRule.Id ? updatedRule : rule);

      this.subscriptions.push(this.dataService.updateRule(updatedRule).subscribe(
        response => {
          this.snackBar.openFromComponent(SuccessComponent, {
            duration: 3000,
            data: `Rule added`
          });
          }, error => {
            this.snackBar.openFromComponent(ErrorComponent, {
              duration: 10000,
              data: 'Internal Server Error'
            });
          }
        ));
      }
    })
  );
}
like image 123
Ares Avatar answered Nov 08 '25 01:11

Ares