为什么MatDialog组件中的Angular Form非常慢?

时间:2019-11-13 19:58:15

标签: angular material-design angular-cdk

我在“材质对话框”组件中具有“角度”形式。有双向绑定的数据,当在输入中切换或键入输入时,屏幕在两次按下之间会锁定几秒钟。所有数据都可以正常传递,但是在尝试使用表单时速度很慢。

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

这是chrome中的效果跟踪器的屏幕截图: Slow down performance over 10 seconds.

我的配置有问题吗?还是在最新的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",
}

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

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

包含以下形式的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>

对话框组件文件:

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接口:

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

*已编辑,其中包括trackBy函数和IDropdownOption接口,以查看唯一标识符。 *

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

3 个答案:

答案 0 :(得分:0)

我会回答,因为我想我知道是什么导致您的速度下降。除了trackBy缺少*ngFor方法之外,它还必须检查您每次输入的整个模板。建议使用OnPush更改检测策略。这可能会使某些事情无法按您期望的方式工作,但这是使组件保持快速运行的好方法,因为只有在更改输入(和其他事情)后,才会检查更改。

在这个问题上,这仍然是不错的article

您应该将对话框组件装饰器更改为包括OnPush

@Component({
  selector: 'app-edit-dialog',
  templateUrl: './edit-dialog.component.html',
  styleUrls: ['./edit-dialog.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})

答案 1 :(得分:0)

非常感谢大家的帮助。我通过在父组件中使用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'
            });
          }
        ));
      }
    })
  );
}

答案 2 :(得分:0)

此答案取决于用例。 可能听起来很奇怪!

背景:我已经在我的自定义网格/表格组件中实现了 OnPush 策略以及分离重新附加的 ChangeDetectorRef,其中很少有元素在 renderer2 的帮助下使用指令添加了玻璃覆盖

我在打开对话框时添加了模糊背景作为“backdropClass”属性。

当屏幕尺寸更大时会导致延迟,如果我减小屏幕尺寸,它会表现良好。如果我从 Dom 树中删除几个元素,它会表现得很好。

所以我发现它是慢慢应用的背景类,导致整个对话滞后的错觉。

只需将其删除即可。

希望这能帮助那些对已经实施的所有解决方案感到头疼的人:)

 this.dialog
  .open(CreateKeyComponent, {
    disableClose: true,
    // backdropClass: 'blur',  -----> root cause
    position: { top: '15vh' },
    data: {
      data: this.tableDataShowFlag ? '1' : '0',
    },
  })