Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 7 ,Reactive Form slow response when has large data

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.

like image 973
dam Avatar asked Feb 05 '19 10:02

dam


People also ask

Are reactive forms scalable?

Reactive forms are more robust: they're more scalable, reusable, and testable. If forms are a key part of your application, or you're already using reactive patterns for building your application, use reactive forms. Template-driven forms are useful for adding a simple form to an app, such as an email list signup form.

Which is better reactive forms or template driven?

Template Driven Forms are based only on template directives, while Reactive forms are defined programmatically at the level of the component class. Reactive Forms are a better default choice for new applications, as they are more powerful and easier to use.

When should we use reactive forms in Angular?

Reactive forms provide a model-driven approach to handling form inputs whose values change over time. This guide shows you how to create and update a basic form control, progress to using multiple controls in a group, validate form values, and create dynamic forms where you can add or remove controls at run time.

What is pristine in Angular reactive form?

A control is pristine if the user has not yet changed the value in the UI. A control is dirty if the user has changed the value in the UI. True if the control is marked as touched .

What is reactive form programming in Angular 7?

Reactive form programming in Angular 7. In Angular, there are two ways to work with web forms: template driven forms and reactive forms. In template driven form, we use angular directives to build the internal representation of form in the html template file.

How to work with web forms in angular?

In Angular, there are two ways to work with web forms: template driven forms and reactive forms. In template driven form, we use angular directives to build the internal representation of form in the html template file.

How do I use reactive form controls in ngmodule?

To use the reactive form controls, import ReactiveFormsModule from the @angular/forms package and add it to your NgModule’s imports array. // app.module.ts import { ReactiveFormsModule } from '@angular/forms' ; imports: [ ...

What is Ang angular form control?

Angular is a platform for building mobile and desktop web applications. ... This guide shows you how to create and update a basic form control, progress to using multiple controls in a group, validate form values, and create dynamic forms where you can add or remove controls at run time.


1 Answers

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:

enter image description here

And here's the summary with the refactored implementation:

enter image description here

like image 79
SiddAjmera Avatar answered Oct 21 '22 21:10

SiddAjmera