components icon indicating copy to clipboard operation
components copied to clipboard

mat-select seems to have serious performance issue

Open naveedahmed1 opened this issue 7 years ago • 24 comments

Bug, feature request, or proposal:

With latest release, mat-form-field support was added for mat-select. But it introduced performance issues when we have multiple select lists (5-8).

When we use mat-form-field with input or text area it doesn't seem to cause any performance issue but it seem that its implementation for mat-select needs a review. On lengthy forms we are seeing a delay of up to 3 seconds, which are instant without mat-form-field on mat-select.

What is the expected behavior?

It should smooth and fast.

What is the current behavior?

Its very slow.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular 4.4.4 Material 2.0.0-beta.12 Chrome

naveedahmed1 avatar Oct 08 '17 00:10 naveedahmed1

It’s not really doing a lot differently from the previous approach. Can you post an example of a form that you consider slow?

crisbeto avatar Oct 08 '17 06:10 crisbeto

Have you noticed performance issues when using mat-form-field with mat-select?

naveedahmed1 avatar Oct 08 '17 11:10 naveedahmed1

I haven't when trying against our various test apps, but your use case might be different.

crisbeto avatar Oct 08 '17 14:10 crisbeto

In my case I have dynamic forms, but till last beta the form in my app were very smooth, but after this update forms with select list are loading slow. Can you please try it with some form and around 5-6 select list?. For clear idea add a button to toggle visibility of form.

naveedahmed1 avatar Oct 08 '17 14:10 naveedahmed1

Can you post an example of a slow form? You can use one of these as a base: https://goo.gl/DlHd6U or https://goo.gl/wwnhMV.

crisbeto avatar Oct 08 '17 14:10 crisbeto

Can you please take a look at https://stackblitz.com/edit/angular-material2-issue-kdt9y4?file=app%2Fapp.component.ts

naveedahmed1 avatar Oct 08 '17 15:10 naveedahmed1

I see. The reason is because all of those 5x100 options are rendered on init, even though they're not displayed, however this is how the select has always worked and definitely hasn't changed since the last beta. We're currently discussing better ways to handle larger lists of options at https://github.com/angular/material2/issues/5113.

crisbeto avatar Oct 08 '17 15:10 crisbeto

If I remove <mat-form-field>, its very smooth, so I think the problem is with <mat-form-field>.

naveedahmed1 avatar Oct 08 '17 16:10 naveedahmed1

I'm not sure I see a difference, but maybe we're talking about different things. I was referring to the delay after toggling the form.

crisbeto avatar Oct 08 '17 16:10 crisbeto

I am also referring to the rendering of form after we click toggle button i.e. delay between the button click and the moment when everything is rendered on screen. And what I am saying is delay is noticeable when we surround mat-select with mat-form-field. I have also gone through https://github.com/angular/material2/issues/5113 may be its due to this issue but in my case its noticeable after this change .

naveedahmed1 avatar Oct 08 '17 16:10 naveedahmed1

One more thing, if you open few select lists from form, then toggle form to hide it and then open again, it will load even slower.

naveedahmed1 avatar Oct 08 '17 23:10 naveedahmed1

Also, conditionally hiding options throws an error.

Consider this case:

<mat-form-field>
  <mat-select placeholder="Country" [(ngModel)]="value" (blur)="onBlur()" class="select-country-main">
    <ng-template ngFor let-country [ngForOf]="countries" [ngForTrackBy]="trackByCountryId">
      <mat-option *ngIf="!isExcluded(country)" [value]="country">
        <country-flag [country]="country"></country-flag>
      </mat-option>
    </ng-template>
  </mat-select>
</mat-form-field>

You get an error:

Error: mat-form-field must contain a MatFormFieldControl.

Before this would work perfectly.

jagomf avatar Nov 20 '17 19:11 jagomf

@jagomf Are you sure you're using the latest version of material? Your example seems to work fine: https://stackblitz.com/edit/angular-material2-issue-dtceox?file=app/app.component.ts

Also please file a separate issue rather than tacking on to unrelated issues

mmalerba avatar Nov 20 '17 21:11 mmalerba

You're right @mmalerba, updating everything to 5.0 seems to work fine. However, this makes for a number of prior versions in which it wouldn't work fine (in my case it was Angular 4.4.6 with material 2.0 beta 11).

jagomf avatar Nov 24 '17 13:11 jagomf

Still facing issues while having multiple mat-select with "multiple" property.

@angular/material: 5.0.2 Angular: 5.1.1 @angular/cdk: 5.0.2

imchaitanya9 avatar Jan 10 '18 10:01 imchaitanya9

I'm facing a similar issue, but in my case I generate 48 options per mat-select and I generate 7 mat-select. My code is similar to this

These are my angular dependencies on my package.json "@angular/animations": "^5.0.0", "@angular/cdk": "^5.0.0", "@angular/common": "^5.0.0", "@angular/compiler": "^5.0.0", "@angular/core": "^5.0.0", "@angular/forms": "^5.0.0", "@angular/http": "^5.0.0", "@angular/material": "^5.0.0", "@angular/platform-browser": "^5.0.0", "@angular/platform-browser-dynamic": "^5.0.0", "@angular/router": "^5.0.0",

ronaldo127 avatar Feb 01 '18 04:02 ronaldo127

I was able to resolve this by ensuring my mat-form-field was inside a form. Outside a form performance was abysmal.

zachberger avatar Jul 02 '18 18:07 zachberger

I was using mat select with @ngrx but my select had only 5 options, and it took about 5 seconds for options to appear.

when I used a <select> there was no problem.

chaimmw avatar Dec 18 '18 20:12 chaimmw

I'll admit that issue still exists in fresh version of Angular only with 10 mat-selects 4 mat-options each for Chrome 72.0.3626.96. Browser stucked API-requests and never rendered.

Versions: "@angular/animations": "^7.2.2", "@angular/cdk": "7.0.1", "@angular/common": "^7.2.2", "@angular/compiler": "^7.2.2", "@angular/core": "^7.2.2", "@angular/forms": "^7.2.2", "@angular/http": "^7.2.2", "@angular/material": "7.0.1", "@angular/material-moment-adapter": "^7.2.2", "@angular/platform-browser": "^7.2.2", "@angular/platform-browser-dynamic": "^7.2.2", "@angular/router": "^7.2.2",

nexen505 avatar Feb 07 '19 07:02 nexen505

Is there any progress on this?

w0wka91 avatar Aug 21 '19 11:08 w0wka91

This should be resolved at earliest, select are essential part of form. In my case I have multi select with 1000+ options and is loads absymally slow.

garg10may avatar Nov 21 '19 06:11 garg10may

Has the performance downgraded further? Countries list with ~250 items now takes 2-3 seconds to open, after I tap/click .

naveedahmed1 avatar Jun 06 '20 09:06 naveedahmed1

I was also having this issue with material select. In our form we have around 12 dropdown boxes in which we had around 10-600 items being loaded per dropdown box. Angular Material select fields were being used for all of these. Our entire form became very slow with a lot of input lag on every input box. The entire form just seemed to choke up. We switched to just using regular native select/option dropdowns and everything was instantly snappy again. Hope this helps someone!

This is what our original mat select looked like:

<mat-form-field style="width: 100px;" appearance="fill">
                    <mat-label>Suffix</mat-label>
                    <mat-select [(ngModel)]="SUFFIX" name="SUFFIX">
                        <mat-option 
                          *ngFor="let suffixItem of suffixList" 
                          value="{{ suffixItem.SUFF_CODE }}">
                          {{ suffixItem.SUFF_CODE }}
                        </mat-option>
                    </mat-select>
</mat-form-field>

We then switched it to be just the native html:

<label>Suffix</label>
        <select formControlName="SUFFIX" name="SUFFIX">
            <option 
                *ngFor="let suffixItem of suffixList" 
                value="{{ suffixItem.SUFF_CODE }}">
                {{ suffixItem.SUFF_CODE }}
            </option>
        </select>

jeohes avatar May 27 '21 23:05 jeohes

As @jelbourn mentioned before, fixing this in Angular Material would require a breaking API change, but for really bad cases with lots of <mat-option>s, I've been experimenting with a custom pipe that will let the currently-selected option(s) through until the select is either opened or the page has been idle long enough to load the rest.

import {
  Pipe,
  PipeTransform,
  OnDestroy,
  ChangeDetectorRef,
} from '@angular/core';
import { MatSelect } from '@angular/material/select';
import { Subscription } from 'rxjs';

/**
 * Initializes only the selected `<mat-option>`s to remove the up-front cost of
 * initializing and rendering off-DOM all of the select options, which can
 * number in thousands and make initial page rendering slow.
 */
@Pipe({ name: 'lazyOptions', pure: false })
export class LazyOptionsPipe implements PipeTransform, OnDestroy {
  private selectedOptionsOnly = true;
  private subscription: Subscription;

  constructor(
    private readonly select: MatSelect,
    private readonly cdr: ChangeDetectorRef
  ) {
    this.subscription = this.select.openedChange.subscribe(() => {
      this.selectedOptionsOnly = false;
    });

    // Preload after some time.
    requestIdleCallback(
      () => {
        this.selectedOptionsOnly = false;
        this.cdr.markForCheck();
      },
      { timeout: 1000 }
    );
  }

  transform<T>(values: T[]): T[] {
    if (!this.selectedOptionsOnly) {
      return values;
    }

    return values.filter((value) => {
      return this.select.compareWith(value, this.select.value);
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

To use, just specify it in your template:

<mat-form-field appearance="fill">
  <mat-label>Select an option</mat-label>
  <mat-select [(value)]="selected">
    <mat-option>None</mat-option>
    <mat-option *ngFor="let value of values | lazyOptions" [value]="value"
      >{{value}}</mat-option
    >
  </mat-select>
</mat-form-field>

mleibman avatar Jul 20 '22 05:07 mleibman

I have the same issue in angular material 15.2.1: if i have a ngif it very slow when i have table with a MatSelect with MatFormField column it is very slow:

with version 9 it works: https://stackblitz.com/edit/angular-material-table-infinite-load-xvpg32?file=src/app/table/table.component.ts

  <div *ngIf="formSubmitted">
    <fe-table-error></fe-table-error>
  </div>
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'fe-condor-table-error',
  templateUrl: './table-error.component.html',
  styleUrls: ['./table-error.component.scss']
})
export class TableErrorComponent implements OnInit, AfterViewInit {
  entity: any;

  displayedColumns = ['position', 'name', 'weight', 'symbol'];
  dataSource: any;
  datos: any = [];
  start: number = 0;
  limit: number = 1000;
  end: number = this.limit + this.start;
  selectedRowIndex: number = null;
  listFormControl = new FormControl();

  moreInfoList = [
    {
      name: 'Detalle del subcontrato',
    },
    {
      name: 'Detalle del contrato',
    },
  ];

  constructor() {
  }

  ngOnInit(): void {
    this.datos = Array.from({length: 500}, () => ({
      id: Math.floor(Math.random() * 100) + 1,
      position: 3,
      name: 'Lithium',
      weight: 6.941,
      symbol: 'Li',
    }));

    this.dataSource = this.getTableData(this.start, this.end);
    this.updateIndex();
  }

  onTableScroll(e: any) {
    console.log(e);
    const tableViewHeight = e.target.offsetHeight; // viewport
    const tableScrollHeight = e.target.scrollHeight; // length of all table
    const scrollLocation = e.target.scrollTop; // how far user scrolled

    // If the user has scrolled within 200px of the bottom, add more data
    const buffer = 200;
    const limit = tableScrollHeight - tableViewHeight - buffer;
    if (scrollLocation > limit) {
      let data = this.getTableData(this.start, this.end);
      this.dataSource = this.dataSource.concat(data);
      this.updateIndex();
    }
  }

  getTableData(start: any, end: any) {
    return this.datos.filter((value: any, index: any) => index >= start && index < end);
  }

  updateIndex() {
    this.start = this.end;
    this.end = this.limit + this.start;
  }

  selectedRow(row: any) {
    console.log('selectedRow', row);
  }

  ngAfterViewInit(): void {
    console.log('### ------------> performance.now() \n', performance.now());
    console.timeEnd('Execution Time');
  }
}

export interface Element {
  name: string;
  position: number;
  weight: number;
  symbol: string;
  age: number;
}

const ELEMENT_DATA: Element[] = [];

<div class="example-container mat-elevation-z8">
  <mat-table #table [dataSource]="dataSource" (scroll)="onTableScroll($event)">

    <!--- Note that these columns can be defined in any order.
          The actual rendered columns are set as a property on the row definition" -->

    <!-- Position Column -->
    <ng-container matColumnDef="position">
      <mat-header-cell *matHeaderCellDef> No.</mat-header-cell>
      <mat-cell *matCellDef="let element">
        {{element.position}}

        <button mat-raised-button color="primary">{{ "SEARCH" }}</button>

        <mat-form-field appearance="fill">
          <mat-label>ss</mat-label>
          <mat-select>
            <mat-option class="red-text" *ngFor="let el of moreInfoList" [value]="el">
              <a>{{ el.name }}</a>
            </mat-option>
          </mat-select>
        </mat-form-field>
      </mat-cell>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <mat-header-cell *matHeaderCellDef> Name</mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
    </ng-container>

    <!-- Weight Column -->
    <ng-container matColumnDef="weight">
      <mat-header-cell *matHeaderCellDef> Weight</mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.weight}} </mat-cell>
    </ng-container>

    <!-- Symbol Column -->
    <ng-container matColumnDef="symbol">
      <mat-header-cell *matHeaderCellDef> Symbol</mat-header-cell>
      <mat-cell *matCellDef="let element"> {{element.symbol}} </mat-cell>
    </ng-container>

    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns; let i= index" (click)="selectedRow(row)"
             [ngClass]="{'highlightTableColor': selectedRowIndex == i}"></mat-row>
  </mat-table>
</div>

pookdeveloper avatar Mar 21 '23 08:03 pookdeveloper