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:

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?
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'
});
}
));
}
})
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With