mercredi 24 mai 2023

How to correctly set mat-checkbox values in formArray, reading saved data from API and having default value (false) for new formArray elements?

I'm working on a mat-stepper structure, in order to create the entity "Rental Contracts". I can create a new rental contract starting from zero, or open a saved draft and continue filling data and saving it to the database usinn entity framework API's.

One rental contract can refer to n vehicles, that the customer will use during the contract validity period.

Each vehicle is represented in a formArray element. Each vehicle can be owned by the rental house, or subleased, and have a number of properties to be filled by the user. Almost all these properties are working well, except the checkbox.

I've created a mat-checkbox to show if the selected machine is subleased or not. I need three different behaviours:

  1. When I create an empty element for the formArray, I need to set "false" value to the checkbox.
  2. Once the user selects a machine code, the system checks if it is subleased, and sets the checkbox selection to "true" ("or "false" otherwise).
  3. If I loaded data from draft, the checkbox needs to be set depending on what I previously saved on the db.

If I use formControl name, I am not able to manage values coming from API. If I use ngModel, I can read from DB but formArray does not work properly when I add a new element to it.

What I need is to understand how to configure my formArray element in order to get working all of the needed behaviours.

My form looks like this:

enter image description here

Here is my "mixed" code (I have tried several changes and commented some code that was not working):

component.ts:

[...]
  vehiclesForm = new FormGroup({
    stocks: new FormArray([])
  });

[...]
  ngOnInit(): void {
    this.vehiclesForm = this.formBuilder.group({
      stocks: this.formBuilder.array([])
    });
  }

  loadData(contractData: any) {
    this.contractData = contractData;
    //console.log('contract-vehicles - contractData', contractData);
    this.getAllBuildingSitesByCustomerId(contractData.customerId);
    this.rentalTypeId = contractData.rentalTypeId;
    this.secondRentalTypeId = contractData.secondRentalTypeId;
    //console.log("this.contractData.rentalStocks", this.contractData.rentalStocks);

    if ((<FormArray>this.vehiclesForm.get("stocks")).length === 0) {
      if (this.contractData.rentalStocks != null && this.contractData.rentalStocks?.length > 0) {

        this.getRentalStocksDetail(this.contractData.rentalStocks).subscribe(
          (stockResults) => {
            this.contractData.rentalStocks.forEach((rentalStock, index) => {
              const stock = stockResults[index];
              const stockIdControl = new FormControl(stock, autocompleteObjectValidator());
              this.filteredStocks[index] = stockIdControl.valueChanges.pipe(
                debounceTime(300),
                switchMap((value) => this.getStocks(value, index))
              );
              this.addStockToFormArray(rentalStock, stockIdControl);
              this.onStockSelectionChange(stock, index);
            });
          },
          (err) => {
            console.log("contract-vehicles.component: getRentalStocksDetail:", err);
          },
        );
      } else {
        this.addNewStockToFormArray();
      }
    }
  }

  getRentalStocksDetail(stocks: any[]): Observable<any[]> {
    let calls = [];
    stocks.forEach(stock => { calls.push(this.stockService.getStock(stock.stockId, true)); });
    return forkJoin(calls);
  }

  getControls() {
    //console.log("<FormArray>this.vehiclesForm.get('stocks')).controls",(<FormArray>this.vehiclesForm.get("stocks")).controls);
    return (<FormArray>this.vehiclesForm['controls'].stocks['controls']);
  }

  getStocks(filter: any, index: number): Observable<any[]> {
    if (filter?.length < 2 || typeof filter !== 'string') {
      return of([]);
    }

    this.queryStock.find = filter;
    this.queryStock.startDate = moment(((<FormGroup>(<FormArray>this.vehiclesForm.get("stocks")).controls[index])).controls.startDate.value).format('YYYY-MM-DDT00:00:00');
    this.queryStock.endDate = moment(((<FormGroup>(<FormArray>this.vehiclesForm.get("stocks")).controls[index])).controls.endDate.value).format('YYYY-MM-DDT23:59:59');
    return this.rentalStockService.getAvailableRentalStocks(this.queryStock).pipe(
      map((response) => response),
      tap((response: any[]) => {
        return response.sort((a, b) => {
          if (a.ownerRef === b.ownerRef) {
            return a.registrationNumber < b.registrationNumber ? -1 : 1
          } else {
            return a.ownerRef < b.ownerRef ? -1 : 1
          }
        });
      })
    );
  }

  displayStock(stock: any): string {
    var desc: string;
    if (stock && stock.id) {
      desc = stock.registrationNumber;
      //console.log("stock.ownerRef", stock.ownerRef);
      if (stock.ownerRef) {
        desc = stock.ownerRef + ' - ' + desc;
      }
    }
    return desc;
  }

  addNewStockToFormArray() {
    if (!(<FormArray>this.vehiclesForm.get('stocks')).valid) {
      return;
    }
    const formLength = (<FormArray>this.vehiclesForm.get('stocks')).length;
    const stockIdControl = new FormControl(null, autocompleteObjectValidator());
    this.filteredStocks[formLength] = stockIdControl.valueChanges.pipe(
      debounceTime(300),
      switchMap((value) => this.getStocks(value, formLength))
    );
    this.addStockToFormArray(null, stockIdControl);
  }

  public addStockToFormArray(rentalStock: any, stockControl: FormControl) {
    console.log("rentalStock", rentalStock);
    console.log("subleased", rentalStock?.subleased);
    const checkDefaultValues = <FormGroup>(<FormArray>this.vehiclesForm.get("stocks")).controls[0];
    (<FormArray>this.vehiclesForm.get("stocks")).push(
      new FormGroup({
        stockId: new FormControl(rentalStock ? rentalStock.id : 0),
        startDate: new FormControl(rentalStock?.startDate ?? this.contractData.startDate),
        endDate: new FormControl(rentalStock?.endDate ?? this.contractData.expiryDate),
        stock: stockControl,
        //subleased: new FormControl({value: rentalStock ? rentalStock.subleased : false, disabled: true}),
      })
    );
  }

  onStockSelectionChange(myStock: any, j: number, fromUI: boolean = false) {
    console.log("onStockSelectionChange", fromUI);
    let formArray = (<FormArray>this.vehiclesForm.get("stocks")).controls;
    formArray[j].get('stockId').setValue(myStock.id);
    //formArray[j].get('subleased').setValue(myStock.subleased);
    if (fromUI) {
      this.contractData.rentalStocks[j].subleased = myStock.subleased;
    }
  }

HTML:

<form [formGroup]="vehiclesForm">
    <table style="width: 50%;">
        <thead>
            <tr>
                <th style="text-align: center;">StartDate</th>
                <th style="text-align: center;">EndDate</th>
                <th style="text-align: center;">Stock</th>
                <th style="text-align: center;">Subleased</th>
                <th style="text-align: center;">Delete</th>
            </tr>
        </thead>
        <tbody formArrayName="stocks">
            <tr *ngFor="let stock of getControls(); let i = index" [formGroupName]="i">
                <!-- Start Date -->
                <td style="text-align: center;">
                    <div class="hidden">
                        <input type="text" class="form-control" formControlName="stockId">
                    </div>
                    <mat-form-field appearance="outline" class="field-width-120 w-100-p mb-12">
                        <mat-label></mat-label>
                        <input matInput [matDatepicker]="startDatePicker"
                            formControlName="startDate"
                            [min]="contractData.startDate" [max]="contractData.expiryDate">
                        <mat-datepicker-toggle matSuffix [for]="startDatePicker">
                        </mat-datepicker-toggle>
                        <mat-datepicker #startDatePicker></mat-datepicker>
                    </mat-form-field>
                </td>
                <!-- End Date -->
                <td style="text-align: center;">
                    <mat-form-field appearance="outline" class="field-width-120 w-100-p mb-12">
                        <mat-label></mat-label>
                        <input matInput [matDatepicker]="endDatePicker"
                            formControlName="endDate"
                            [min]="contractData.startDate" [max]="contractData.expiryDate">
                        <mat-datepicker-toggle matSuffix [for]="endDatePicker">
                        </mat-datepicker-toggle>
                        <mat-datepicker #endDatePicker></mat-datepicker>
                    </mat-form-field>
                </td>
                <!-- Stock -->
                <td style="text-align: center;">
                    <mat-form-field appearance="outline" class="field-width-stock w-100-p mb-12">
                        <mat-label></mat-label>
                        <input matInput formControlName="stock" [value]="stock"
                            [matAutocomplete]="autoStock" type="text" autocomplete="off">
                        <mat-autocomplete #autoStock="matAutocomplete" [displayWith]="displayStock">
                            <mat-option *ngFor="let myStock of filteredStocks[i] | async"
                                [value]="myStock" (onSelectionChange)="onStockSelectionChange(myStock, i, true)">
                                
                            </mat-option>
                        </mat-autocomplete>
                    </mat-form-field>
                </td>
                <!-- Subleased -->
                <td style="vertical-align: initial; text-align: center;">
                    <mat-checkbox class="h5" [ngModel]="contractData.rentalStocks[i]?.subleased" [ngModelOptions]="{standalone: true}" disabled></mat-checkbox>
                </td>
                <!-- Delete -->
                <td style="vertical-align: initial; text-align: center;">
                    <button mat-icon-button color="warn"
                        (click)="onDeleteItem(i); $event.preventDefault()">
                        <mat-icon>delete</mat-icon>
                    </button>
                </td>
            </tr>
            <tr>
                <button mat-raised-button class="header-button mt-24 mt-md-0"
                    (click)="addNewStockToFormArray(); $event.preventDefault()">
                    <mat-icon>add</mat-icon>
                </button>
            </tr>
        </tbody>
    </table>
</form>

Any suggestion/help will be much appreciated!

Many thanks and best regards,

Laura




Aucun commentaire:

Enregistrer un commentaire