为什么 Angular Form 在 MatDialog 组件中很慢?

Ada*_*dan 3 material-design angular angular-cdk

我在 Material Dialog 组件中有一个 Angular 表单。数据是双向绑定的,当通过输入键切换或输入输入时,会导致屏幕在 keydown 的 . 所有数据都正确传递,但在尝试使用表单时速度非常慢。

我尝试重构用于输入的表单以使用“材料表单”,但仍然具有相同的减速性能。

这是 chrome 中性能跟踪器的屏幕截图: 降低性能超过 10 秒。

我的配置有问题吗?或者这是最新的 Angular 8 动画/CDK 包中可能的回归?这是我的 Angular 包依赖项:

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",
}
Run Code Online (Sandbox Code Playgroud)

这是调用对话框的组件方法:

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'
          });
          }
        ));
      }
    })
  );
}
Run Code Online (Sandbox Code Playgroud)

包含表单的 mat 对话框模板:

<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>
Run Code Online (Sandbox Code Playgroud)

对话框组件文件:

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;
  }

}
Run Code Online (Sandbox Code Playgroud)

IDropdownOption 接口:

export interface IDropdownOption {
  Text: string;
  Value: number;
}
Run Code Online (Sandbox Code Playgroud)

*编辑以包含 trackBy 函数和 IDropdownOption 接口以查看唯一标识符。*

速度变慢似乎是因为下拉选项被反复循环……也许需要更改 changeDetection 策略?

Ada*_*dan 5

非常感谢大家的帮助。我通过在父组件中使用 ChangeDetectorRef 在对话框打开后分离并在对话框关闭后重新附加来解决我的问题。这可以防止任何重新渲染/重新绘制 EditDialogComponent 并修复性能问题。

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'
            });
          }
        ));
      }
    })
  );
}
Run Code Online (Sandbox Code Playgroud)