web-dev-qa-db-fra.com

Angular 7, réponse lente sous forme réactive en cas de données volumineuses

J'ai des données très complexes et volumineuses et je dois construire une forme réactive autour d'elle dans un composant.

J'ai développé le formulaire. Mais lorsque je tape quelque chose dans l'un des champs input pour modifier les données remplies, il répond extrêmement lentement en mettant à jour la valeur de ce champ.

J'ai essayé d'utiliser updateOn:'blur' et 'submit' mais sans aucune chance.

Ma question est, quelle est la meilleure pratique pour gérer le formulaire avec des données volumineuses?

Mise à jour : Ceci est mon StackBlitz .

Remarque : J'ai créé une version très minimale de mon implémentation réelle et j'ai des problèmes de performances dans le formulaire réactif.

14
dam

Donc, après environ une journée de jeu avec votre StackBlitz, je suis là avec la solution. Je pense que cela améliorerait considérablement les performances.

Étape 1: créer des interfaces pour votre modèle de données

Cela rendrait le code plus propre et plus lisible. Cela rendrait également le code plus facile à gérer et à utiliser. Nous allons donc ici avec la liste des interface pour votre scénario spécifique:

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

Étape 2: modifier la façon dont vous créez le formulaire

La façon dont vous créez le formulaire est extrêmement bruyante. Il y a une façon claire de le faire. Et puisque vous créez déjà le formulaire dans le service, je vous suggère de conserver la tâche de créer le formulaire pour le service lui-même et de garder votre composant libre de toute tâche de ce type. Ainsi, votre service peut être refactorisé comme ceci:

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

}

Étape 3: Tirez parti du service ci-dessus:

Faites-le pour obtenir le formulaire et vous débarrasser des methods qui vous renverraient les FormArrays dans votre modèle. Cela rendrait votre composant très propre, clair et concis.

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) {
  }

}

Étape 4: Refactorisez votre modèle:

Et celui-ci est LE PLUS IMPORTANT . [~ # ~] jamais [~ # ~] appeler des getters ou des méthodes sous des formes profondément imbriquées pour obtenir les FormArrays. Ou plutôt, sous des formes normales ou dans une syntaxe de liaison de données en général. Parce qu'ils seront appelés à chaque cycle de détection de changement et tueraient les performances de votre application.

Veuillez vous référer à cet exposé éclair de Tanner Edwards de ng-conf 2018 pour en savoir plus.

Ainsi, vous pouvez refactoriser votre modèle de composant comme ceci:

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

Étape 5: ne vous arrêtez pas ici

Ce n'est pas la fin. C'est juste le début. Vous pouvez également résumer le formulaire enfant (le marketGroupFormGroup à l'intérieur du marketGroupsFormArray) dans un composant distinct. Et puis vous pouvez définir son changeDetectionStrategy sur OnPush. Cela vous donnerait une performance encore meilleure.

Voici un StackBliz auquel vous pouvez vous référer pour jeter un œil à cette solution.

Faire tout cela améliorerait considérablement les performances du formulaire.

J'espère que ça aide. J'essaierai de mettre à jour cette réponse si je trouve autre chose pour améliorer les performances au-delà de cette limite.


Voici un exemple de travail StackBlitz pour votre référence.


Voici un article moyen détaillé à ce sujet que j'ai écrit pour AngularInDepth .


Différence de performances

Je viens de faire un audit de performance sur votre implémentation ainsi que le mien pour le même ensemble d'étapes effectuées sur l'application.

Voici le résumé de votre implémentation:

enter image description here

Et voici le résumé avec l'implémentation refactorisée:

enter image description here

22
SiddAjmera