Angular 7,当有大数据时,Reactive Form 响应缓慢 [英] Angular 7 ,Reactive Form slow response when has large data

查看:36
本文介绍了Angular 7,当有大数据时,Reactive Form 响应缓慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个非常复杂和庞大的数据,我必须在一个组件中围绕它构建一个响应式表单.

我已经开发了表格.但是,当我在 input 字段之一中键入内容以编辑填充的数据时,它在更新该字段的值时响应极其缓慢.

我尝试使用 updateOn:'blur''submit' 但没有任何运气.

我的问题是,处理包含大量数据的表单的最佳做法是什么?

更新:这是我的

这是重构实施的总结:

I have a very complex and large data and I have to build a Reactive form around it in a component.

I've developed the form. But when I type something in one of the input fields to edit the populated data, it responds extremely slowly in updating the value of that field.

I tried using updateOn:'blur' and 'submit' but without any luck.

My question is, what is best practice to handle the form with large data?

Update: This is my StackBlitz.

Note: I've created a very minimal version of my actual implementation and I have performance issues in the Reactive Form.

解决方案

So after about a day of playing around with your StackBlitz, here I am with the solution. I think this would significantly improve the performance.

Step 1: Create interfaces for your Data Model

Doing that would significantly make the code cleaner and more readable. It would also make the code more manageable and easy to work with. So here we go with the list of interfaces for your specific scenario:

export interface Hotel {
  id: string;
  currencyId: string;
  hotelYearId: string;
  priceTaxTypeId: string;
  code: string;
  name: string;
  createBy: string;
  createDate: string;
  lastUpdateBy: string;
  lastUpdateDate: string;
  remark: string;
  internalRemark: string;
  roomTypes: RoomType[];
}

export interface RoomType {
  chk: boolean;
  roomTypeId: string;
  mealTypes: MealType[];
}

export interface MealType {
  chk: boolean;
  mealTypeId: string;
  marketGroups: MarketGroup[];
}

export interface MarketGroup {
  chk: boolean;
  markets: Market[];
  rateSegments: RateSegment[];
}

export interface Market {
  marketId: string;
}

export interface RateSegment {
  chk: boolean;
  rateSegmentId: string;
  hotelSeasons: HotelSeason[];
}

export interface HotelSeason {
  chk: boolean;
  hotelSeasonId: string;
  rates: Rate[];
}

export interface Rate {
  rateCodeId: string;
  cancellationPolicyId: string;
  dayFlag: string;
  singlePrice: string;
  doublePrice: string;
  xbedPrice: string;
  xbedChildPrice: string;
  bfPrice: string;
  bfChildPrice: string;
  unitMonth: string;
  unitDay: string;
  minStay: number;
}

Step 2: Change the way you're creating the form

The way you're creating the form is extremely noisy. There's a clear way of doing that. And since you're already creating the form in the service, I suggest you keep the task of creating the form to the service itself and keep your component free from any such task. So your service can be refactored like this:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormBuilder, Validators } from '@angular/forms';
import { map } from 'rxjs/operators';

import { Hotel, RoomType, MealType, MarketGroup, Market, RateSegment, HotelSeason, Rate } from './hotel.model';

@Injectable()
export class UtilService {

  constructor(
    private readonly fb: FormBuilder,
    private readonly http: HttpClient
  ) { }

  getHotelForm() {
    return this.getHotel().pipe(
      map((apiResponse: any) => this.fb.group({
        id: [apiResponse.id, Validators.required],
        currencyId: [apiResponse.currencyId, Validators.required],
        hotelYearId: [apiResponse.hotelYearId, Validators.required],
        priceTaxTypeId: [apiResponse.priceTaxTypeId, Validators.required],
        code: [apiResponse.code, Validators.required],
        name: [apiResponse.name, Validators.required],
        createBy: [apiResponse.createBy, Validators.required],
        createDate: [apiResponse.createDate, Validators.required],
        lastUpdateBy: [apiResponse.lastUpdateBy, Validators.required],
        lastUpdateDate: [apiResponse.lastUpdateDate, Validators.required],
        remark: [apiResponse.remark, Validators.required],
        internalRemark: [apiResponse.internalRemark, Validators.required],
        roomTypes: this.fb.array(apiResponse.roomTypes.map(roomType => this.generateRoomTypeForm(roomType)))
      }))
    );
  }

  private getHotel() {
    return this.http.get('/assets/hotel.json');
  }

  private generateRoomTypeForm(roomType: RoomType) {

    const roomTypeForm = this.fb.group({
      chk: [roomType.chk, Validators.required],
      roomTypeId: [roomType.roomTypeId, Validators.required],
      mealTypes: this.fb.array(roomType.mealTypes.map(mealType => this.generateMealTypeForm(mealType)))
    });

    return roomTypeForm;
  }

  private generateMealTypeForm(mealType: MealType) {

    const mealTypeForm = this.fb.group({
      chk: [mealType.chk, Validators.required],
      mealTypeId: [mealType.mealTypeId, Validators.required],
      marketGroups: this.fb.array(mealType.marketGroups.map(marketGroup => this.generateMarketGroupForm(marketGroup)))
    });

    return mealTypeForm;
  }

  private generateMarketGroupForm(marketGroup: MarketGroup) {

    const marketGroupForm = this.fb.group({
      chk: [marketGroup.chk, Validators.required],
      markets: this.fb.array(marketGroup.markets.map(market => this.generateMarketForm(market))),
      rateSegments: this.fb.array(marketGroup.rateSegments.map(rateSegment => this.generateRateSegmentForm(rateSegment))),
    });

    return marketGroupForm;
  }

  private generateMarketForm(market: Market) {
    return this.fb.group({
      marketId: [market.marketId, Validators.required]
    });
  }

  private generateRateSegmentForm(rateSegment: RateSegment) {
    const rateSegmentForm = this.fb.group({
      chk: [rateSegment.chk, Validators.required],
      rateSegmentId: [rateSegment.rateSegmentId, Validators.required],
      hotelSeasons: this.fb.array(rateSegment.hotelSeasons.map(hotelSeason => this.generateHotelSeasonForm(hotelSeason)))
    });

    return rateSegmentForm;
  }

  private generateHotelSeasonForm(hotelSeason: HotelSeason) {

    const hotelSeasonForm = this.fb.group({
      chk: [hotelSeason.chk, Validators.required],
      hotelSeasonId: [hotelSeason.hotelSeasonId, Validators.required],
      rates: this.fb.array(hotelSeason.rates.map(rate => this.generateRateForm(rate)))
    });
    return hotelSeasonForm;
  }

  private generateRateForm(rate: Rate) {
    return this.fb.group({
      rateCodeId: [rate.rateCodeId, Validators.required],
      cancellationPolicyId: [rate.cancellationPolicyId, Validators.required],
      dayFlag: [rate.dayFlag, Validators.required],
      singlePrice: [rate.singlePrice, Validators.required],
      doublePrice: [rate.doublePrice, Validators.required],
      xbedPrice: [rate.xbedPrice, Validators.required],
      xbedChildPrice: [rate.xbedChildPrice, Validators.required],
      bfPrice: [rate.bfPrice, Validators.required],
      bfChildPrice: [rate.bfChildPrice, Validators.required],
      unitMonth: [rate.unitMonth, Validators.required],
      unitDay: [rate.unitDay, Validators.required],
      minStay: [rate.minStay, Validators.required]
    });
  }

}

Step 3: Leverage the above service:

Do it to get the Form and get rid of the methods that would return to you the FormArrays in your template. That would make your Component very clean, clear and concise.

import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';

import { UtilService } from '../app/util.service';

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

export class AppComponent {
  
  form$: Observable<FormGroup> = this.util.getHotelForm();

  constructor(private readonly util: UtilService) {
  }

}

Step 4: Refactor your Template:

And this one is THE MOST IMPORTANT. NEVER call getters or methods in deeply nested forms to get the FormArrays. Or rather, in normal forms or inside a data binding syntax in general. Because they will get called in every single change detection cycle and would kill your App's performance.

Please refer to this lightning talk by Tanner Edwards from ng-conf 2018 to know more about it.

So, you can refactor your Component Template like this:

<form 
  *ngIf="form$ | async as form" 
  [formGroup]="form">
    <div 
    formArrayName="roomTypes">
        <div 
      *ngFor="let roomType of form.controls['roomTypes'].controls; let index = index" 
      [formGroupName]="index">
            {{index}}
            <div 
        formArrayName="mealTypes">
                <div 
          *ngFor="let mealType of roomType.controls['mealTypes'].controls; let mealtypeIndex = index"
          [formGroupName]="mealtypeIndex">
                    mealtype {{mealtypeIndex}}
                    <div 
            formArrayName="marketGroups">
                        <div 
              *ngFor="let marketGroup of mealType.controls['marketGroups'].controls; let marketGroupIndex = index" 
              [formGroupName]="marketGroupIndex">
                            marketGroupIndex {{marketGroupIndex}}
                            <div formArrayName="rateSegments">
                                <div 
                  *ngFor="let rateSegment of marketGroup.controls['rateSegments'].controls; let rateSegmentIndex = index"
                  [formGroupName]="rateSegmentIndex">
                                    rateSegmentIndex {{rateSegmentIndex}}
                                    <div formArrayName="hotelSeasons">
                                        <div 
                      class="fifth_border" 
                      *ngFor="let hotelseason of rateSegment.controls['hotelSeasons'].controls; let hotelseasonIndex = index"
                      [formGroupName]="hotelseasonIndex">
                                            hotelseasonIndex {{hotelseasonIndex}}
                                            <div formArrayName="rates">
                                                <div 
                          *ngFor="let rate of hotelseason.controls['rates'].controls; let rateIndex = index"
                          [formGroupName]="rateIndex">
                          <div style="display:flex;flex-flow;row">
                            <div>
                              <p>SGL</p>
                              <input class="input text_right" type="text" formControlName="singlePrice">
                            </div>
                            <div>
                              <p>DLB/TWN</p>
                              <input class="input text_right" type="text"  formControlName="doublePrice">
                            </div>
                            <div>
                              <p>EX-Adult</p>
                              <input class="input text_right" type="text"  formControlName="xbedPrice" >
                            </div>
                            <div>
                              <p>EX-Child</p>
                              <input class="input text_right" type="text"  formControlName="xbedChildPrice">
                            </div>
                            <div>
                              <p>Adult BF</p>
                              <input class="input text_right" type="text"  formControlName="bfPrice">
                            </div>
                            <div>
                              <p>Child BF</p>
                              <input class="input text_right" type="text"  formControlName="bfChildPrice">
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div> 
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- <pre>{{form.value | json}}</pre> -->
</form>

Step 5: Don't stop here

This is not the end. It's just the beginning. You can also abstract the Child Form(the marketGroup FormGroup inside the marketGroups FormArray) into a separate component. And then you can set it's changeDetectionStrategy to OnPush. This would give you an even better performance.

Here's a StackBliz that you can refer to, to have a look at that solution.

Doing all this would significantly improve the performance of the form.

I hope this helps. I'll try to update this answer if I find anything else to improve the performance beyond this limit.


Here's a Working Sample StackBlitz for your ref.


Here's a Detailed Medium Article about this that I wrote for AngularInDepth.


Difference in Performance

I just did a Performance Audit on your Implementation as well as mine for the same set of steps performed on the App.

Here's the summary with your implementation:

And here's the summary with the refactored implementation:

这篇关于Angular 7,当有大数据时,Reactive Form 响应缓慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆