javascript - Legarea datelor între proprietatea elementului html și valoarea scriptului nu este sincronizată


Descrierea problemei:
Există un calendar de disponibilitate care afișează dacă o persoană este ocupată într-un anumit interval (fiecare zi este împărțită în două intervale). Această stare este stocată în matricea booleană 2D isSlotFree (această matrice are dimensiunea 31 x 2). Inițial, toate valorile din această matrice sunt inițializate cu true. Apoi, se face o cerere HTTP GET către un server pentru a solicita datele ocupate. Odată ce acestea sunt primite, funcția setIsSlotFree() este apelată pentru a seta valorile corespunzătoare din matrice la false. În vizualizare (fișierul HTML) există un element <td> pentru fiecare interval. Fiecare dintre aceste elemente își actualizează culoarea (folosind stiluri de clasă) pe baza valorii booleene stocate la indexul relevant din matrice. Problema este că pagina HTML nu reflectă schimbările făcute în array după apelarea funcției setIsSlotFree(). (adică elementele HTML încă văd toate valorile ca adevărate). Totuși, când imprim array-ul în consolă imediat după cererea get, valorile corespunzătoare s-au schimbat la false. Când este declanșat orice eveniment, doar atunci vederea este actualizată cu valorile corecte. Care este problema aici?

Următoarele sunt părțile relevante din fișierul component.ts

export class CalendarComponent implements OnInit {

  viewDate: Date;
  isSlotFree: boolean[][] = [
    [true, true]
  ];

  constructor(private http: HttpClient) {

  }


  ngOnInit() {
    this.viewDate = new Date();
    var i: number;
    var j: number;

    for (i = 1; i < 31; i++) {
      this.isSlotFree.push([true, true]);
    }
    let p = new HttpParams().set('month', (this.viewDate.getMonth() + 1).toString());

    this.http.get < busyDateResponse[] > ("http://localhost:51967/api/stylists/getBusyDates", {
      params: p
    }).subscribe(res => {
      this.setIsSlotFree(res);
    });

    this.x = true;
    console.log(this.isSlotFree);
    this.viewDate = new Date();

  }

  setIsSlotFree(res: busyDateResponse[]): void {
    var busy_date: busyDateResponse;
    for (busy_date of res) {
      var temp: number = (busy_date.slot == 'm') ? 0 : 1;
      this.isSlotFree[busy_date.day - 1][temp] = false;
    }

  }
}

interface busyDateResponse {
  $id: string;
  day: number;
  month: number;
  year: number;
  slot: string;
}

Următorul arată părțile relevante ale fișierului component.html

<ng-template #cellTemplate let-day="day" let-locale="locale">
  <div class="cal-cell-top">
    <div class="cal-day-number w3-xlarge">{{ day.date | calendarDate:'monthViewDayNumber':locale }}</div>
    <br>

  </div>
  <div *ngIf="day.inMonth && day.isFuture">
    <table style="width:100%">

      <tr>
        <td class="calendar-slot" [ngClass]="{'w3-green': isSlotFree[day.date.getDate()-1][0], 'w3-red': !isSlotFree[day.date.getDate()-1][0]}">{{isSlotFree[day.date.getDate()-1][0]}}Morning</td>
        <mat-checkbox (change)="editSelectedSlots($event)" [checked]="isSelectedSlot(day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_m')?true:false" [id]="day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_m'"
          *ngIf="isSlotFree[day.date.getDate()-1][0]"></mat-checkbox>
      </tr>
      <tr>
        <td class="calendar-slot" [ngClass]="{'w3-green': isSlotFree[day.date.getDate()-1][1], 'w3-red': !isSlotFree[day.date.getDate()-1][1]}">{{isSlotFree[day.date.getDate()-1][1]}}Evening</td>
        <mat-checkbox (change)="editSelectedSlots($event)" [checked]="isSelectedSlot(day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_e')?true:false" [id]="day.date.getDate() + '_' + day.date.getMonth() + '_' + day.date.getFullYear() + '_e'"
          *ngIf="isSlotFree[day.date.getDate()-1][1]">
        </mat-checkbox>
      </tr>
    </table>
  </div>

</ng-template>

<div>
  <mwl-calendar-month-view [viewDate]="viewDate" [events]="events" (eventClicked)="eventClicked($event)" (dayClicked)="dayClicked($event)" [cellTemplate]="cellTemplate" [refresh]="refresh">
  </mwl-calendar-month-view>
  <div class="w3-center">
    <button mat-raised-button>Make booking</button>
  </div>
</div>

Rețineți că <mwl-calendar-month-view> utilizează ng-template pentru a genera celule într-un calendar.

Răspuns #1

Ei bine, am găsit în sfârșit soluția după ce am citit mult despre detectarea schimbărilor în Angular. Strategia de detectare a schimbărilor este executată înainte de rularea funcției setIsSlotFree(). Chiar dacă valorile din array sunt modificate, strategia de detectare a schimbărilor nu este executată atunci când valorile array-ului sunt modificate de această funcție. Prin urmare, detectarea schimbărilor trebuie executată manual după ce toate modificările au fost aplicate în array. Acest lucru se poate face folosind ChangeDetectorRef.detectChanges() la sfârșitul funcției setIsSlotFree().

Dacă constructorul pentru componentă este constructor(private http:HttpClient, private ref: ChangeDetectorRef), atunci,
funcția isSlotFree() ar arăta astfel,

setIsSlotFree(res:busyDateResponse[]):void {
    var busy_date:busyDateResponse;
    for(busy_date of res) {
        var temp:number = (busy_date.slot=='m')?0:1;
        this.isSlotFree[busy_date.day-1][temp] = false;
    }
    this.ref.detectChanges();        
  }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>